Composite: Tree Structure Handling
The Problem It Solves
Menus contain items; menus also contain submenus containing more menus and items. GUI panels contain buttons and other panels. Org charts, file systems, bill-of-materials — tree structures everywhere, and client code drowns in if (node instanceof Leaf) ... else recurse into children. Composite removes that branching by giving leaves and containers one shared interface: clients call the same operation on anything in the tree; recursion happens invisibly inside composites.
┌─────────────┐
│ «Component» │ operation() ← ONE interface for all nodes
│ + add(c) │◄───────── clients hold Component refs only
│ + remove(c) │
└──────△──△───┘
implements │ │ implements
┌───────────────┘ └──────────────┐
┌──────┴──────┐ ┌──────┴────────┐
│ Leaf │ │ Composite │
│ MenuItem │ │ SubMenu │
│ op(): self │ │ children list │
└─────────────┘ │ op(): for each child → child.op()
└───────────────┘
RECURSION lives only inside composites — clients never see it
Mechanics
interface MenuComponent {
void render(StringBuilder sb);
default void add(MenuComponent c) {
throw new UnsupportedOperationException("leaf");
}
}
class MenuItem implements MenuComponent { // LEAF
private final String label;
MenuItem(String label) { this.label = label; }
public void render(StringBuilder sb) { sb.append("- ").append(label).append("\n"); }
}
class SubMenu implements MenuComponent { // COMPOSITE
private final String title;
private final List<MenuComponent> children = new ArrayList<>();
SubMenu(String title) { this.title = title; }
public void add(MenuComponent c) { children.add(c); } // real add/remove here
public void render(StringBuilder sb) {
sb.append("+ ").append(title).append("\n");
for (MenuComponent c : children) c.render(sb); // uniform recursion
}
}
Client code becomes structure-agnostic:
MenuComponent main = new SubMenu("Main");
main.add(new MenuItem("Home"));
SubMenu settings = new SubMenu("Settings"); // nested composite — same type
settings.add(new MenuItem("Theme"));
main.add(settings);
main.render(sb); // one call walks everything
Design Decisions
| Question | Options |
|---|---|
Where do add/remove live? | Component (transparency — leaves get throwing defaults, as above) vs Composite-only (type safety, but clients must downcast) |
| Child ordering | List preserves insertion order; sets when uniqueness matters |
| Parent pointers | Needed for upward navigation (selection propagation); costs sync maintenance |
Real-World Sightings
java.awt.Container/Componenthierarchy — the pattern’s origin story.- JSF/Angular component trees, XML DOM
Node, file system walkers. - Organization charts and permission trees (
Rolecontaining sub-roles).
Operations Beyond Render
Cost aggregation, size calculation, permission checks (“user has any of these roles” over a role tree), serialization — all become single calls recursing through the uniform interface. Adding a new operation across the whole tree still means touching every node class (or pairing with Visitor for stable hierarchies).
Trade-offs & Pitfalls
- Gains: client simplicity; open-closed growth of tree shapes; recursion centralized.
- Costs: over-general interfaces (leaves carrying meaningless
add); hard to enforce structural constraints (max depth, child types) through the uniform type. - Cycle guard: allowing a composite to be added under its own descendant creates infinite recursion — production implementations track ancestors or reject re-parenting.
Interview Framing
- File-system/menu questions are canonical; producing the shared-interface sketch plus the cycle-guard remark covers pattern and production axes.
Premium Content
Unlock Composite and all premium lessons with a subscription.
From ₹199.99/year — See plans