Composite: The Folders-and-Files Pattern
Look at your computer's file system. A file has a size. A folder contains files — and other folders, which contain more files and folders, as deep as you like.
Now ask: "What's the size of this folder?"
You don't care whether it holds files or folders. You just ask the folder, and it figures it out — adding up everything inside, however deep it goes. A folder with one file and a folder with 500 nested subfolders answer the same question the same way.
That's the Composite pattern: treat a single object and a whole tree of objects identically. A leaf (file) and a branch (folder) respond to the same call through the same interface.
The problem it solves
Without it, your code fills up with "is this one thing or many things?" checks:
// Constantly asking: am I dealing with a file or a folder?
if (item is File) total += ((File)item).Size;
else if (item is Folder)
foreach (var child in ((Folder)item).Children)
// ...now recurse manually, checking types again
Type-checks everywhere. Every new level of nesting means more branches. Fragile and ugly.
The Composite way
Everyone — file and folder — shares one interface:
public interface IFileSystemItem
{
int GetSize();
}
The leaf — a single item with nothing inside:
public class FileItem : IFileSystemItem
{
private readonly int _size;
public FileItem(int size) => _size = size;
public int GetSize() => _size; // just returns its own size
}
The composite — holds children, which are themselves IFileSystemItem:
public class Folder : IFileSystemItem
{
private readonly List<IFileSystemItem> _children = new();
public void Add(IFileSystemItem item) => _children.Add(item);
public int GetSize()
{
int total = 0;
foreach (var child in _children)
total += child.GetSize(); // ask each child — don't care what it is
return total;
}
}
Notice the loop calls child.GetSize() without ever asking "file or folder?" If the child is a file, it returns its size. If it's a folder, it recurses into its own children. The recursion is invisible and automatic.
var root = new Folder();
root.Add(new FileItem(100));
root.Add(new FileItem(200));
var sub = new Folder();
sub.Add(new FileItem(50));
sub.Add(new FileItem(75));
root.Add(sub); // a folder inside a folder — no problem
Console.WriteLine(root.GetSize()); // 425 — one call walks the whole tree
But where do you actually use this?
The file/folder example explains the mechanism. Here's where it shows up in real work:
- UI component trees. Angular, React, and WPF are Composite. A component contains components; calling "render" on the root renders the whole tree. If you build a reusable component library, you're building composites.
-
Nested menus and navigation. A menu item (leaf) and a menu group (composite) both need
Render()andIsVisible(user). One recursive call renders the entire sidebar and respects permissions at every level. -
Org and approval hierarchies. A manager contains employees; some employees are managers.
GetAllReports()on the top boss walks the whole tree with zero type-checking. -
Rule and permission trees. A rule can be a single condition or a group of rules (
ALL of these/ANY of these), nested to any depth. A condition and a group both answerEvaluate(user). This is how validation and access-policy engines are built. - Document structures. JSON, XML, HTML, folder trees — anything you'd draw as a tree. Parsing, sizing, searching, exporting: all one recursive call.
When NOT to use it
Be honest about your data. Composite earns its place only when you have a genuine tree — things containing things of their own type, to arbitrary depth. If your data is a flat list, a plain List<T> is fine. Don't force a tree where there isn't one.
The test: "Am I running the same operation over one item and over an entire subtree, and I want both to be the same line of code?" If yes, Composite. If no, skip it.
One line to remember
Composite = folders and files. A single item and a whole tree of items answer the same call the same way — and recursion handles itself.
Part 8 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton, Factory Method, Builder, Adapter, Decorator, Facade, and Proxy. Next up: Observer — the notification pattern.
Top comments (1)
Canaidh sinn Rose Tree ris.