Design PatternsStructural

Composite

Treat individual objects and compositions of objects uniformly through a common interface.

3 min readDesigns · LLD & Patterns

Intent

Compose objects into tree structures, and let clients work with leaves and branches the same way.

Use when:

  • Your data forms a part-whole hierarchy (file system, GUI tree, org chart, expression tree).
  • You want clients to ignore the difference between a leaf and a group.

Real-world Analogy

A folder. It contains files (leaves) and other folders (composites). When you ask for the size of a folder, you get the sum of everything inside, regardless of whether each child is a file or another folder. Clients say "give me the size" without caring which it is.


Structure

StructureClass diagram

Example: File System

public interface FsNode {
    String name();
    long sizeBytes();
}

public class File implements FsNode {
    private final String name;
    private final long size;

    public File(String name, long size) {
        this.name = name;
        this.size = size;
    }

    public String name() { return name; }
    public long sizeBytes() { return size; }
}

public class Directory implements FsNode {
    private final String name;
    private final List<FsNode> children = new ArrayList<>();

    public Directory(String name) { this.name = name; }

    public void add(FsNode node) { children.add(node); }
    public void remove(FsNode node) { children.remove(node); }

    public String name() { return name; }

    @Override
    public long sizeBytes() {
        return children.stream().mapToLong(FsNode::sizeBytes).sum();   // recursion!
    }
}

Usage

Directory root = new Directory("/");
root.add(new File("readme.md", 1024));

Directory src = new Directory("src");
src.add(new File("Main.java", 4096));
src.add(new File("Utils.java", 2048));

root.add(src);

System.out.println(root.sizeBytes());   // 7168 — handles arbitrary depth

The caller treats File and Directory identically through FsNode.sizeBytes(). The recursion lives inside Directory.


Class Diagram

Class DiagramClass diagram

Other Examples

GUI Tree

interface UIComponent { void render(); }
class Label implements UIComponent { public void render() { /*...*/ } }
class Panel implements UIComponent {
    List<UIComponent> children;
    public void render() { children.forEach(UIComponent::render); }
}

Expression Tree

interface Expr { int eval(); }
class Num implements Expr { int v; public int eval() { return v; } }
class Add implements Expr {
    Expr left, right;
    public int eval() { return left.eval() + right.eval(); }
}
class Mul implements Expr {
    Expr left, right;
    public int eval() { return left.eval() * right.eval(); }
}

// (2 + 3) * 4
Expr e = new Mul(new Add(new Num(2), new Num(3)), new Num(4));
e.eval();  // 20

Org Chart

interface Employee { double totalSalary(); }
class IndividualContributor implements Employee {
    double salary;
    public double totalSalary() { return salary; }
}
class Manager implements Employee {
    double salary;
    List<Employee> reports;
    public double totalSalary() {
        return salary + reports.stream().mapToDouble(Employee::totalSalary).sum();
    }
}

The Add/Remove Trade-off

The classic question: should add() / remove() live on the Component interface (uniform but unsafe) or only on Composite (type-safe but non-uniform)?

ApproachProCon
On ComponentCaller doesn't need to downcastLeaf.add() either no-ops, throws, or awkwardly succeeds
On Composite onlyType-safe — Leaf has no addCaller may need to check type and cast

GoF prefers transparency (on Component); modern Java tends toward safety (on Composite). Your call.


Real-world Examples

Use caseComposite
File systemFile / Directory
GUI treeComponent / Container
HTML / XML DOMText / Element
Org chartEmployee / Manager
Math expressionsNumber / BinaryOp
Menu hierarchyMenuItem / Menu

Trade-offs

✅ Pros:

  • Uniform treatment of leaves and composites — clients are simpler
  • Easy to add new node types (open/closed)
  • Recursion is contained inside composites

❌ Cons:

  • Can be hard to constrain children types (e.g., a MathExpr shouldn't accept arbitrary children)
  • Add/remove on Component weakens type safety
  • Deep trees have stack overhead during recursion

Interview Tips

  • Use composite when the interviewer's domain is naturally tree-shaped: files, GUI, expressions, hierarchies.
  • Mention the recursion is inside Composite — the leaf is trivial.
  • Bring up the add/remove placement question proactively; it shows you've thought about API design.

Finished reading?

Mark it done to track your progress in Designs.