
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
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.
Related Patterns
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
- Why can an empty Folder return zero through the same Interface as File?
- When would the naive solution on this page be easier to maintain? Give a concrete example.
- 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
- Overview: all 23 patterns, their categories, and how to choose
- Previous: Bridge (Structural Pattern)
- Next: Decorator (Structural Pattern)