The idea
What it is
Composite solves a very common shape of problem: your objects naturally form a tree. A folder holds files and other folders. A UI panel holds buttons and other panels. An order holds products and bundles of products. The pattern's intent fits in one sentence: compose objects into tree structures, and treat individual objects and compositions of objects uniformly.
Think of your computer's file system. A file has a size. A folder has a size too — it's just the sum of everything inside it. When you right-click a folder and pick Properties, you don't care that it contains 3 files here, a nested folder there, and more files inside that. You ask one question — 'how big is this?' — and get one answer. Composite makes your code work the same way: both File and Folder implement one shared interface (say, FileSystemItem with a size() method), so any code that can handle one item can automatically handle an entire tree of them.
The one sentence to remember
A Leaf does the work itself; a Composite just forwards the same call to its children and combines the results. Because both share one interface, the client can't tell — and doesn't need to tell — a single object from a whole tree.
The magic ingredient is recursion
You never write a loop that 'walks the whole tree'. You write Folder.size() as ask each child for its size and add them up. If a child is a file, it answers directly. If a child is another folder, the very same method runs again one level down. The tree walks itself.
Mechanics
How it works
The three participants
Every Composite setup has the same three roles. Once you can name them, you can spot the pattern anywhere:
- Component — the shared interface, e.g.
FileSystemItemwithsize(). This is the only type client code ever talks to. It declares the operations that make sense for both single items and groups. - Leaf — a plain end object with no children, e.g.
File. It implements the operation directly:size()just returns its own byte count. Leaves do the real work. - Composite — the container, e.g.
Folder. It holds a list of children (each typed asComponent, so files and folders mix freely) and implements the operation by delegating: callsize()on every child and combine the answers.
How one call cascades down the tree
Say you call size() on the root folder project. The folder doesn't know (or care) what its children are — it just calls size() on each one. readme.md answers 2 KB immediately. src is a folder, so the same thing happens one level down: it asks app.ts (14 KB) and utils.ts (6 KB), adds them to 20 KB, and passes that up. Every level does one tiny job — ask children, add — and the recursion assembles the full answer:
interface FileSystemItem {
size(): number; // the one shared operation
}
class File implements FileSystemItem {
constructor(private bytes: number) {}
size(): number { return this.bytes; } // Leaf: answers directly
}
class Folder implements FileSystemItem {
private children: FileSystemItem[] = [];
add(item: FileSystemItem): void { this.children.push(item); }
size(): number { // Composite: delegates + combines
return this.children.reduce((sum, child) => sum + child.size(), 0);
}
}Notice what's missing: Folder.size() has no if (child is File) check. A child is just a FileSystemItem that knows how to report its size. That's the uniformity the pattern buys you — add a new kind of node (a symlink, a zip archive) and Folder doesn't change at all.
Child management: where should add() live?
Composites need child-management methods — add(item), remove(item), maybe getChildren(). The classic design question is where to declare them, and there are two honest answers:
- Transparency — declare
add()/remove()on theComponentinterface itself. Now clients treat everything uniformly, even for child management. The cost:File.add()exists but makes no sense, so a leaf must throw or silently ignore it — a runtime surprise. - Safety — declare
add()/remove()only onComposite(as in the code above). Now it's impossible to add a child to a file — the compiler stops you. The cost: clients that want to build trees must know they're holding aFolder, so uniformity is slightly weaker.
Which to pick?
The GoF book leans toward transparency; most modern codebases lean toward safety, because compile-time errors beat runtime surprises. Keep the operations (like size()) uniform on the Component — that's where uniformity pays off — and keep tree-building on the Composite.
This tree shape is everywhere
Once you see the file-system version, you'll recognise the same structure all over software:
- UI component trees — a window holds panels, panels hold buttons and more panels;
render()on the root renders everything. - Org charts — a manager 'contains' employees and other managers;
headcount()ortotalSalary()rolls up the same waysize()does. - Menus — a menu holds items and submenus;
MenuItemandMenushare one interface. - Arithmetic expressions —
(2 + 3) * 4is a tree where numbers are leaves and operators are composites;evaluate()cascades exactly likesize().
A close cousin worth contrasting: [[decorator]] also wraps objects behind a shared interface, but a Decorator has exactly one child and exists to add behaviour on the way through. A Composite has many children and exists to aggregate their answers. Same wrapping trick, different purpose.
Interactive prototype
See it. Build it. Break it.
A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.
About this simulation
A live file tree. Every row — file or folder — carries the same size() chip; that uniformity is the whole pattern. Click size() on a file and it simply flashes back its own number. Click size() on a folder and watch the call ripple down the subtree: each child lights up in turn, floats its answer back, and the folder shows the summed total in a green badge. One call at the top, recursion does everything below. Select any folder and use + File / + Folder to grow the tree — then call size() again and see the new nodes join the cascade with zero extra code.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Call size() on a leaf, then on a folder
In the prototype, find 📄 readme.md and click the size() chip on its row — the row flashes and answers its own 2 KB. Nothing special. Now click the size() chip on 📁 src: the call ripples down, app.ts and utils.ts each light up and float their numbers back, and src shows the green total 20 KB. Same chip, same call — the only difference is what happens inside.
One call on the root sums the entire tree
Click size() on 📁 project, the top row. Watch the cascade visit every file and folder underneath — each child returns its answer, nested folders sum their own children first, and the root shows the grand total in a green badge. You made exactly one call; recursion did all the walking.
Grow the tree, call again — zero new code
Click the 📁 assets row to select it (a ring appears), then press + File and + Folder to drop new items inside it. Click size() on 📁 project again: the brand-new nodes just join the cascade and the total updates. The folder never needed to know what kinds of children it holds. Press ↺ Reset to start over.
In practice
When to use it — and what trips people up
Use Composite when your problem is genuinely a tree
Two signals tell you this pattern fits. Both should be true:
- You have a part–whole hierarchy — objects that contain other objects of the same general kind, nested to any depth: folders in folders, panels in panels, bundles in bundles, teams in teams.
- Clients should ignore the difference — the code using these objects wants to ask one question (
size(),render(),price(),headcount()) without checking whether it's holding one item or a group. If you find yourself writingif (item is Folder) ... else ...all over, Composite removes exactly those branches.
When NOT to use it
- The structure is flat — a plain list of files with no nesting needs a list, not a pattern. Composite only earns its keep when containers hold other containers.
- Leaves and containers genuinely need different APIs — if clients almost always need folder-only operations (permissions, watching, listing) and almost never treat files and folders alike, forcing one shared interface makes it too general: it fits neither side well.
- The hierarchy is fixed and two levels deep — a report with exactly 'sections containing rows' can be two plain classes. Reach for Composite when depth is open-ended.
Composite vs Decorator, in one line
Both hide behind the component's interface and both delegate to what they wrap. A [[decorator]] wraps one child to add behaviour; a Composite wraps many children to aggregate results. If you're summing, rendering, or iterating over a group — that's Composite.
What it gives you
- Clients get radically simpler — one call like
root.size()handles a single file or a million-node tree, with no type checks and no hand-written tree-walking loops. - Recursion comes for free — each Composite only knows about its direct children, yet operations naturally cascade through arbitrary depth.
- Open for extension — add a new node type (symlink, zip archive, smart folder) that implements the Component interface, and every existing Composite and client works with it unchanged.
- Trees are easy to build and reshape — because children are typed as the Component, you can freely move a subtree, nest deeper, or mix leaves and composites at any level.
Common mistakes
- The Component interface can become over-general — to keep files and folders uniform you may declare methods that only make sense for one side, leaving leaves with awkward empty or throwing implementations.
- Hard to restrict what a composite may contain — the type system says 'any Component fits', so rules like 'a playlist may hold songs but not other playlists' need runtime checks instead of compiler help.
- Transparency vs safety is a real cost either way — put
add()on the interface and leaves get nonsense methods; keep it on the Composite and clients must downcast to build trees. - Debugging and performance need care — one innocent call at the root can fan out into thousands of recursive calls; without caching (e.g. memoised folder sizes) repeated queries on big trees get expensive.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// Component — the one interface both sides share
interface FileSystemItem {
name: string;
size(): number;
}
// Leaf — a plain file; answers size() directly
class File implements FileSystemItem {
constructor(public name: string, private bytes: number) {}
size(): number { return this.bytes; }
}
// Composite — a folder; holds children and delegates
class Folder implements FileSystemItem {
private children: FileSystemItem[] = [];
constructor(public name: string) {}
add(item: FileSystemItem): this { // "safety" style: add() lives here
this.children.push(item);
return this;
}
size(): number { // ask every child, add the answers
return this.children.reduce((sum, c) => sum + c.size(), 0);
}
}
// Build: project/ ├─ readme.md └─ src/ ├─ app.ts └─ utils.ts
const project = new Folder("project");
project.add(new File("readme.md", 2_000));
const src = new Folder("src");
src.add(new File("app.ts", 14_000)).add(new File("utils.ts", 6_000));
project.add(src);
// The client treats a file and a whole tree identically:
const anything: FileSystemItem = project; // could just as well be a File
console.log(anything.size()); // 22000 — one call, whole treeReferences & further reading
5 sources- Articlerefactoring.guru
Composite — Refactoring.Guru
The clearest illustrated walkthrough: the boxes-inside-boxes problem, structure diagrams, and pseudocode plus per-language examples. Best first read.
- Docsen.wikipedia.org
Composite pattern — Wikipedia
Concise reference with UML for the pattern, the transparency-vs-safety discussion, and short implementations in several languages.
- Booken.wikipedia.org
Design Patterns: Elements of Reusable Object-Oriented Software — Gamma, Helm, Johnson, Vlissides
The original Gang of Four book. Its Composite chapter defines the Component/Leaf/Composite roles and argues the transparency side of the add() debate.
- Articlepython-patterns.guide
The Composite Pattern — Brandon Rhodes, Python Patterns
A thoughtful Python take: how duck typing changes the pattern, with the file-system hierarchy as the running example — a great second opinion after the classic treatment.
- Articlesourcemaking.com
Composite Design Pattern — SourceMaking
Compact summary with intent, checklist, and rules of thumb — handy for revision, including when to combine Composite with Iterator or Visitor.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Sign in to save your best score.
question 01 / 05
What is the intent of the Composite pattern?
question 02 / 05
In the file-system example, how does Folder.size() produce its answer?
question 03 / 05
Which role does a File play, and what makes it different from a Folder?
question 04 / 05
In the 'transparency vs safety' debate, what does the safe design choose?
question 05 / 05
Both Composite and Decorator wrap objects behind a shared interface. What's the key difference?
0/5 answered