Composite Pattern explained simply
Thilan Dissanayaka Software Architecture February 11, 2020

Composite Pattern explained simply

Sometimes you have a single object and a group of objects, and you want to treat them exactly the same way. No special cases, no type checking, no “is this a leaf or a container?” branching logic scattered everywhere.

That is the whole point of the Composite Pattern. It lets you compose objects into tree structures, then work with those trees as if every node – whether it’s a single element or a group of elements – is the same thing. One interface to rule them all.

What is the Composite Pattern?

At its core:

  • Build tree structures to represent part-whole hierarchies.
  • Let clients treat individual objects and compositions uniformly.
  • A composite object contains children that can be either leaves or other composites.

The trick is that both the leaf (individual object) and the composite (group of objects) implement the same interface. The client code calls the same method on both, and doesn’t need to know whether it’s talking to a single object or an entire subtree of objects.

Same interface, whether it’s one object or a thousand.

Real-Life Analogy

Think about your file system. You have files and folders. A file is a standalone thing – it has a name, a size, you can open it, delete it, move it. A folder is a container – it holds files and other folders.

But here is the thing. You interact with both of them the same way. You can “open” a file. You can “open” a folder. You can “delete” a file. You can “delete” a folder (and everything inside it goes too). You can “move” a file. You can “move” a folder.

Your file manager doesn’t care whether you right-clicked a file or a folder. The operations are the same. The folder just happens to recursively apply those operations to its children.

That is the Composite Pattern. A folder is a composite that contains components (files and other folders), and they all share the same interface.

Structure

Here is a UML class diagram showing how the pieces fit together. The UIComponent interface is implemented by both leaf nodes (Button, TextField) and the composite node (Panel), which holds a list of children.

<mxfile>
  <diagram name="Composite Pattern">
    <mxGraphModel dx="1100" dy="780" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="700">
      <root>
        <mxCell id="0"/>
        <mxCell id="1" parent="0"/>

        <!-- UIComponent interface -->
        <mxCell id="2" value="&lt;&lt;interface&gt;&gt;&#xa;UIComponent&#xa;─────────────────&#xa;+ render(): void&#xa;+ getSize(): int" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
          <mxGeometry x="370" y="30" width="260" height="90" as="geometry"/>
        </mxCell>

        <!-- Button (leaf) -->
        <mxCell id="3" value="Button&#xa;─────────────────&#xa;- label: String&#xa;+ render(): void&#xa;+ getSize(): int" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
          <mxGeometry x="80" y="230" width="200" height="90" as="geometry"/>
        </mxCell>

        <!-- TextField (leaf) -->
        <mxCell id="4" value="TextField&#xa;─────────────────&#xa;- placeholder: String&#xa;+ render(): void&#xa;+ getSize(): int" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
          <mxGeometry x="310" y="230" width="200" height="90" as="geometry"/>
        </mxCell>

        <!-- Panel (composite) -->
        <mxCell id="5" value="Panel&#xa;─────────────────&#xa;- children: List&lt;UIComponent&gt;&#xa;+ render(): void&#xa;+ getSize(): int&#xa;+ addComponent(c: UIComponent): void&#xa;+ removeComponent(c: UIComponent): void" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
          <mxGeometry x="560" y="220" width="310" height="110" as="geometry"/>
        </mxCell>

        <!-- Client -->
        <mxCell id="6" value="Client" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
          <mxGeometry x="30" y="50" width="120" height="50" as="geometry"/>
        </mxCell>

        <!-- Client uses UIComponent -->
        <mxCell id="7" value="uses" style="endArrow=open;endSize=12;dashed=1;strokeColor=#d6b656;" edge="1" source="6" target="2" parent="1">
          <mxGeometry relative="1" as="geometry"/>
        </mxCell>

        <!-- Button implements UIComponent -->
        <mxCell id="8" style="endArrow=block;endFill=0;dashed=1;strokeColor=#82b366;" edge="1" source="3" target="2" parent="1">
          <mxGeometry relative="1" as="geometry"/>
        </mxCell>

        <!-- TextField implements UIComponent -->
        <mxCell id="9" style="endArrow=block;endFill=0;dashed=1;strokeColor=#82b366;" edge="1" source="4" target="2" parent="1">
          <mxGeometry relative="1" as="geometry"/>
        </mxCell>

        <!-- Panel implements UIComponent -->
        <mxCell id="10" style="endArrow=block;endFill=0;dashed=1;strokeColor=#b85450;" edge="1" source="5" target="2" parent="1">
          <mxGeometry relative="1" as="geometry"/>
        </mxCell>

        <!-- Panel aggregates UIComponent (diamond) -->
        <mxCell id="11" value="children *" style="endArrow=open;endSize=12;startArrow=diamond;startFill=1;startSize=14;strokeColor=#6c8ebf;exitX=0.85;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" source="5" target="2" parent="1">
          <mxGeometry relative="1" as="geometry">
            <Array as="points">
              <mxPoint x="824" y="170"/>
              <mxPoint x="824" y="75"/>
            </Array>
          </mxGeometry>
        </mxCell>
      </root>
    </mxGraphModel>
  </diagram>
</mxfile>

Java Example: UI Component Tree

This is the kind of structure behind frameworks like Java Swing, JavaFX, and pretty much every UI toolkit out there. Let’s build a simplified version.

First, the UIComponent interface. Every component – whether it’s a single button or a panel containing dozens of elements – implements this:

public interface UIComponent {
    void render();
    int getSize();
}

Two simple operations. render() draws the component. getSize() returns how much space it takes up. Nothing fancy.

Now the leaf nodes. These are the actual UI elements that don’t contain other components.

Button – a simple leaf that renders itself and has a fixed size:

public class Button implements UIComponent {
    private final String label;

    public Button(String label) {
        this.label = label;
    }

    @Override
    public void render() {
        System.out.println("  [Button: " + label + "]");
    }

    @Override
    public int getSize() {
        return 1;
    }
}

TextField – another leaf with its own rendering behavior:

public class TextField implements UIComponent {
    private final String placeholder;

    public TextField(String placeholder) {
        this.placeholder = placeholder;
    }

    @Override
    public void render() {
        System.out.println("  [TextField: " + placeholder + "]");
    }

    @Override
    public int getSize() {
        return 2;
    }
}

A text field takes up a bit more space than a button, so its size is 2. In a real framework, these would be pixel calculations, but you get the idea.

Now the star of the show. The Panel is the composite – it holds a list of UIComponent children and delegates operations to all of them:

import java.util.ArrayList;
import java.util.List;

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

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

    public void addComponent(UIComponent component) {
        children.add(component);
    }

    public void removeComponent(UIComponent component) {
        children.remove(component);
    }

    @Override
    public void render() {
        System.out.println("[Panel: " + name + "] (size=" + getSize() + ")");
        for (UIComponent child : children) {
            child.render();
        }
        System.out.println("[/Panel: " + name + "]");
    }

    @Override
    public int getSize() {
        int total = 0;
        for (UIComponent child : children) {
            total += child.getSize();
        }
        return total;
    }
}

What’s happening here? When you call render() on a Panel, it announces itself, then iterates through all its children and calls render() on each one. If a child is another Panel, that Panel does the same thing – renders itself, then renders its children. Recursion handles the entire tree without any extra logic.

The getSize() method works the same way. It sums up the sizes of all its children. If a child is a Panel, that Panel sums up its own children. The total bubbles up automatically.

Notice that Panel doesn’t care whether its children are Buttons, TextFields, or other Panels. It just calls the UIComponent interface methods. That is the power of the pattern.


Building a Form

Let’s wire this up and build a realistic UI structure – a login form with nested panels:

public class MainProgram {
    public static void main(String[] args) {
        // Create leaf components
        Button loginButton = new Button("Login");
        Button cancelButton = new Button("Cancel");
        TextField usernameField = new TextField("Enter username");
        TextField passwordField = new TextField("Enter password");

        // Create a panel for form inputs
        Panel inputPanel = new Panel("Input Fields");
        inputPanel.addComponent(usernameField);
        inputPanel.addComponent(passwordField);

        // Create a panel for action buttons
        Panel buttonPanel = new Panel("Actions");
        buttonPanel.addComponent(loginButton);
        buttonPanel.addComponent(cancelButton);

        // Create the root panel that holds everything
        Panel formPanel = new Panel("Login Form");
        formPanel.addComponent(inputPanel);
        formPanel.addComponent(buttonPanel);

        // Render the entire form with a single call
        System.out.println("=== Rendering the form ===");
        formPanel.render();

        // Get the total size with a single call
        System.out.println("\nTotal form size: " + formPanel.getSize());
    }
}

Output:

=== Rendering the form ===
[Panel: Login Form] (size=6)
  [Panel: Input Fields] (size=4)
    [TextField: Enter username]
    [TextField: Enter password]
  [/Panel: Input Fields]
  [Panel: Actions] (size=2)
    [Button: Login]
    [Button: Cancel]
  [/Panel: Actions]
[/Panel: Login Form]

Total form size: 6

One call to formPanel.render() and the entire tree renders itself. One call to formPanel.getSize() and you get the total size of every component in the hierarchy. The client code doesn’t walk the tree manually. It doesn’t check types. It just calls methods on the root, and the composite structure handles the rest.

That is the Composite Pattern doing exactly what it was designed to do.

Key Components

  • Component (UIComponent): Declares the interface for all objects in the composition. Both leaves and composites implement this.
  • Leaf (Button, TextField): Represents the end objects in the composition. A leaf has no children. It implements the component interface directly.
  • Composite (Panel): Stores child components and implements child-related operations like addComponent() and removeComponent(). Delegates the component interface methods to its children.
  • Client: Manipulates objects through the Component interface. It never needs to know whether it is dealing with a leaf or a composite.

When to Use the Composite Pattern

  • When you need to represent part-whole hierarchies of objects.
  • When you want clients to be able to ignore the difference between individual objects and compositions of objects.
  • When you have a tree structure and need to perform operations on both individual nodes and entire subtrees.
  • When adding new component types should not require changes to the existing client code.

Advantages and Disadvantages

Advantages:

  • Uniform treatment: Clients use the same interface for individual objects and compositions. No type checking, no special cases.
  • Easy to add new component types: Just implement the Component interface. Existing code doesn’t change.
  • Simplifies client code: The client doesn’t need to know the tree structure. It just calls methods on the root and recursion does the work.
  • Open/Closed Principle: You can introduce new leaf or composite types without modifying existing code.

Disadvantages:

  • Can make the design overly general: When you want to restrict what types of children a composite can hold, the Composite Pattern makes that harder to enforce at compile time.
  • Harder to restrict component types: Since everything shares the same interface, you might end up relying on runtime checks to prevent invalid compositions.
  • Leaf nodes inherit unnecessary methods: If you put addComponent() and removeComponent() in the Component interface (some implementations do), leaf nodes have to deal with methods that don’t make sense for them.

Real-World Use Cases

  • Java Swing/AWT: The java.awt.Component and java.awt.Container hierarchy is a textbook Composite Pattern. A JPanel contains JButton, JLabel, and other JPanel instances – all treated as Component.
  • DOM tree: The browser’s Document Object Model is a composite structure. Every node is either an element (which can have children) or a text node (which can’t). Methods like appendChild(), removeChild(), and querySelectorAll() work the same way on both.
  • Organizational charts: An organization has departments. Departments have teams. Teams have people. You can ask “what’s the headcount?” at any level and it sums up recursively.
  • Menu systems: A menu can contain menu items (leaves) and submenus (composites). Clicking “render” on the top-level menu draws the entire menu tree.
  • File systems: As discussed in the analogy – files are leaves, directories are composites. Operations like calculating total size, copying, and deleting work recursively through the tree.

Wrapping Up

The Composite Pattern lets you build tree structures where clients treat individual objects and groups of objects through the same interface. No type checking, no branching logic, no “is this a leaf or a container?” nonsense.

If you have ever worked with Java Swing, the DOM, or even a file system, you have already used this pattern without knowing it. A panel contains buttons and other panels. A div contains spans and other divs. A folder contains files and other folders. Same idea, every time.

The next time you find yourself building a tree structure where operations need to cascade from parent to children, reach for the Composite Pattern. Define a common interface, make your leaves implement it directly, make your composites delegate to their children, and let recursion do the heavy lifting.

ALSO READ
Writing a Shell Code for Linux
Apr 21, 2020 Exploit Development

Shellcode is a small piece of machine code used as the payload in exploit development. In this post, we write Linux shellcode from scratch — starting with a simple exit, building up to spawning a shell, and explaining every decision along the way.

Exploiting a Stack Buffer Overflow on Windows
Apr 12, 2020 Exploit Development

In a previous tutorial we discusses how we can exploit a buffer overflow vulnerability on a Linux machine. I wen through all theories in depth and explained each step. Now today we are going to jump...

Exploiting a  Stack Buffer Overflow  on Linux
Apr 01, 2020 Exploit Development

Have you ever wondered how attackers gain control over remote servers? How do they just run some exploit and compromise a computer? If we dive into the actual context, there is no magic happening....

Basic concepts of Cryptography
Mar 01, 2020 Cryptography

Ever notice that little padlock icon in your browser's address bar? That's cryptography working silently in the background, protecting everything you do online. Whether you're sending an email,...

Common Web Application Attacks
Feb 05, 2020 Application Security

Web applications are one of the most targeted surfaces by attackers. This is primarily because they are accessible over the internet, making them exposed and potentially vulnerable. Since these...

Remote Code Execution (RCE)
Jan 02, 2020 Application Security

Remote Code Execution (RCE) is the holy grail of application security vulnerabilities. It allows an attacker to execute arbitrary code on a remote server — and the consequences are as bad as it sounds. In this post, we'll go deep into RCE across multiple languages, including PHP, Java, Python, and Node.js.

> > >