Skip to content
Sanfor2004
← All articles
Design PatternsDesign PatternsStructural PatternsPythonC++Software Engineering

Composite (Structural Pattern)

By Ahmed (Sanfor) · 11 Sept 2026 · 5 min read
Shipping boxes contain smaller boxes and individual items in a nested structure.

Start with the design patterns overview · Part 08 of 23 · Structural patterns

Treat individual objects and nested groups through the same component interface. This article follows the example in my 23 Design Patterns repository, connecting the problem, participating classes, Python and C++20 implementations, and the trade-offs that decide whether to use it.

The Problem

A file browser must compute bytes for a file or a folder containing nested folders.

Naive Solution

int total = file_size;
for (int size : folder_sizes) total += size; // only one nesting level

Why It Becomes a Problem

Special loops for each depth break as nesting grows and repeat file-versus-folder checks.

The Idea

Give File and Folder the Entry interface (The contract of operations and observable behavior offered to a caller). A folder recursively asks its children for bytes.

Reading the illustration

A shipping box can contain products or smaller boxes. Asking any box for its weight recursively gives the weight of everything inside.

In the repository example, the same design idea addresses this software problem: A file browser must compute bytes for a file or a folder containing nested folders.

Structure

Diagram · Python source · C++20 source

Composite sketch map: Client::bytes() leads through Entry to File / Folder[Entry].
Composite: trace the example from caller through the pattern boundary to its collaborator or result. The arrows show flow, not ownership. Open the full-size diagram.
Client::bytes()  -->  Entry  -->  File / Folder[Entry]

Participants

Entry defines bytes. File returns its size; Folder owns children with std::unique_ptr (A smart pointer with exclusive ownership that releases its object when the owner is destroyed) and aggregates their results.

Canonical roles in this example:

  • Component — The common contract exposed by leaves, groups, or wrappers. Here: Entry.
  • Leaf — A Component with no child Components. Here: File.
  • ownership — Responsibility for keeping a resource alive and eventually releasing it. Here: Folder::children_.

Python Example

The complete Python source is shown first.

class File:
    def __init__(self, size):
        if size < 0:
            raise ValueError("Negative size")
        self.size = size

    def bytes(self):
        return self.size


class Folder:
    def __init__(self):
        self.children = []

    def add(self, child):
        self.children.append(child)

    def bytes(self):
        return sum(child.bytes() for child in self.children)


def main():
    root = Folder()
    print("Empty:", root.bytes(), "bytes")
    images = Folder()
    images.add(File(20))
    root.add(File(10))
    root.add(images)
    print("Total:", root.bytes(), "bytes")
    try:
        File(-1)
    except ValueError:
        print("Negative size rejected")


if __name__ == "__main__":
    main()

Python Output

Empty: 0 bytes
Total: 30 bytes
Negative size rejected

Code Walkthrough

Entry defines bytes. File returns its size; Folder owns children with std::unique_ptr and aggregates their results.

Start at the final call in the Python example. Follow the middle role in the diagram and compare how the C++20 version handles the same responsibility.

C++20 Example

#include <iostream>
#include <memory>
#include <stdexcept>
#include <utility>
#include <vector>

struct Entry {
    virtual ~Entry() = default;
    virtual int bytes() const = 0;
};
class File final : public Entry {
    int size_;
public:
    explicit File(int size) : size_(size) {
        if (size < 0) throw std::invalid_argument("Negative size");
    }
    int bytes() const override { return size_; }
};
class Folder final : public Entry {
    std::vector<std::unique_ptr<Entry>> children_;
public:
    void add(std::unique_ptr<Entry> child) {
        if (!child) throw std::invalid_argument("Null child");
        children_.push_back(std::move(child));
    }
    int bytes() const override {
        int total = 0;
        for (const auto& child : children_) total += child->bytes();
        return total;
    }
};
int main() {
    auto images = std::make_unique<Folder>();
    images->add(std::make_unique<File>(20));
    Folder root;
    root.add(std::make_unique<File>(10));
    root.add(std::move(images));
    std::cout << "Total: " << root.bytes() << " bytes\n";
    std::cout << "Empty: " << Folder{}.bytes() << " bytes\n";
    try { const File invalid{-1}; }
    catch (const std::invalid_argument&) { std::cout << "Negative size rejected\n"; }
}

C++20 Output

Total: 30 bytes
Empty: 0 bytes
Negative size rejected

When to Use

Use it for genuine part-whole trees where a useful operation applies to both leaves and groups.

Use cases

File trees, scene graphs without sharing, and menu hierarchies are suitable contexts.

When NOT to Use

Avoid it for a flat list or a graph with shared parents and cycles; tree ownership would misrepresent the domain.

Advantages

Clients calculate a subtree total without knowing its depth or concrete shape.

Trade-offs

Very deep trees can overflow the call stack, integer sums can overflow, and group-only operations should not be forced onto leaves.

Decorator · Iterator

Common Confusion

Decorator wraps one object to add behavior. Composite normally owns multiple children to represent a whole; both can recurse through an interface.

Terms to Remember

  • Composite — Treat a leaf and a tree of objects through the same operation.
  • Component — The common contract exposed by leaves, groups, or wrappers. Example: Entry.
  • Leaf — A Component with no child Components. Example: File.
  • ownership — Responsibility for keeping a resource alive and eventually releasing it. Example: Folder::children_.

Interview Vocabulary

  • part-whole hierarchy — A recursive structure in which groups contain leaves or smaller groups.
  • recursive composition — Building a structure from parts that expose the same contract as the whole.
  • polymorphism — Using one interface with different implementations; C++ supports runtime and compile-time forms.

Interview Question

Why is add available on Folder rather than Entry? What would a File.add mean?

Mini Challenge

Add an empty folder and a second nesting level; verify both totals and consider a wider size type.

Compare the two versions

Python uses a list of Objects that offer bytes; C++ uses a common Entry Interface and exclusive Ownership with unique_ptr. Python references allow accidental shared children or cycles. Keep this example a tree; garbage collection does not make recursive traversal of a cycle safe. The two examples express the same pattern responsibility; compare their setup and output before changing an input.

Check yourself

  1. Why can an empty Folder return zero through the same Interface as File?
  2. When would the naive solution on this page be easier to maintain? Give a concrete example.
  3. Change one input in the Python example. Predict the output and explain which responsibility handles the change.

Run and explore the example

The Python code comes from python/main.py, with expected output. The C++20 code comes from structural/composite/cpp/main.cpp. Follow the repository’s C++20 build instructions to compile it and compare the result with expected.txt. The output demonstrates this example’s behavior; it does not cover every input or the challenge above.

The source example and adapted explanation are © 2026 Sanfor2004, provided under the MIT license. The sketchbook cover is an illustration preserved from this site’s original pattern lessons.

Continue the series

Ahmed (Sanfor)Backend and automation engineer in Alexandria. I build the systems businesses run on, and paint between deploys.

Keep reading

Design PatternsAbstract Factory (Creational Pattern)5 minDesign PatternsAdapter (Structural Pattern)4 minDesign PatternsBridge (Structural Pattern)5 min

Have manual work eating your week?

Book a free call