Before you start
File System is the definitive Composite pattern design problem. If module 3.10 clicked, this lesson writes itself.
The Composite signal, unmistakable
File and Directory both need size(). Directory's
size is the sum of its children's sizes. If ANY child
is a File, use its size. If ANY child is a Directory,
RECURSE.
This is exactly the Composite payoff: size() on the
ROOT walks the whole tree without caring what's a leaf
and what's a container. sum(c.size() for c in children) is one line — no isinstance needed.
Your design.md Patterns section should name
Composite AND say why the isinstance-free recursion is
the win.
The signal check
Ask: "would my Directory code contain
isinstance(child, File) or isinstance(child, Directory)?"
If yes → you're branching on kind, which is exactly the smell Composite removes. Fix: unify under a Node interface where both types answer the same questions (size, walk, ls).
If no → Composite is doing its job.
Path parsing
Paths look complex but they're mechanical:
- Absolute (starts with
/) — strip leading/, split on/, drop empty strings. /→[]= root./foo/bar→["foo", "bar"].foo/bar(no leading /) → invalid, raise.
Walk from root: for each component, go into the child Directory. If missing OR the current node is a File, raise.
The rules
Three matter:
- No orphaned children. Every Node lives in exactly one parent's children dict. delete() removes it from that dict; no other references exist.
- Root can't be deleted. Enforced by the check at the top of delete().
- Every non-root path has exactly one parent. Enforced by the mkdir/create validation — you can only add a node inside an existing parent Directory.
Each has a clear enforcement point. Name them.
Where this shows up in real code
- Every operating system's file system (obviously)
- Version control systems (git tracks trees this way)
- Cloud object stores' "folder" abstraction
- IDE project trees
- JSON documents (value / array / object all walked uniformly)
- DOM (Element / TextNode both walked)
All the same shape. Composite is one of the most-used patterns in production code.
Interviewer follow-ups
- "How would you add symlinks?" — new Node subclass that forwards operations to its target. Careful about cycles.
- "How would you make this thread-safe?" — a lock per directory, or one big lock. Complex tradeoffs.
- "How would you handle 1M files?" — memory concern. Move to lazy-load from disk. Different problem — you've been building an in-memory FS.