Deep Modules is John Ousterhout's design principle from A Philosophy of Software Design: a module should offer a lot of functionality behind a small, stable interface. Because every interface element is a cost callers must pay, deep modules hide design decisions likely to change, while shallow modules, whose interface is nearly as complex as their implementation, push complexity onto callers.
Deep Modules
Details
- Also known as
-
Module Depth, Deep Classes
Core Concepts:
- Module depth
-
The ratio of benefit (functionality provided) to cost (interface complexity). A deep module delivers powerful functionality behind a small, stable interface; a shallow module exposes nearly as much complexity as it contains.
- Shallow module
-
A module whose public interface is almost as complex as its implementation. Shallow modules shift the complexity burden onto callers rather than hiding it — adding interface cost without proportional abstraction benefit.
- Interface as cost
-
Every element of a public API — parameters, types, configuration options, side effects — is cognitive cost that every caller must pay. Deep modules minimise this cost while maximising what they deliver.
- Information hiding
-
Modules should encapsulate design decisions likely to change: data structures, algorithms, file formats, protocols. Those decisions remain invisible to callers, so the interface stays stable even as the implementation evolves.
- Classitis
-
Ousterhout’s term for the anti-pattern of decomposing a system into many tiny, shallow classes. Each class adds its own interface to the collective cognitive load without reducing overall implementation complexity.
- Different layer, different abstraction
-
Each architectural layer should offer abstractions meaningfully different from the layer below. A layer that only renames lower-level calls without adding substance is a shallow passthrough and a sign of poor design.
- Key Proponents
-
John Ousterhout ("A Philosophy of Software Design", 2nd ed., Yaknyam Press, 2021; 1st ed. 2018 — Chapter 4: "Modules Should Be Deep")
When to Use:
-
Designing a module’s public API — evaluate whether the interface is simpler than the implementation it hides
-
Reviewing a proposed class decomposition for whether it reduces complexity or merely redistributes it
-
Identifying pass-through methods and wrapper chains that add interface cost without hiding detail
-
Instructing an LLM to design APIs or class boundaries with minimal caller-visible surface area
-
Choosing between a general-purpose and a special-purpose API — prefer the deeper, more general option
Related Anchors:
Criticism:
-
Andrey Lebedev, "Not My Philosophy of Software Design" (Medium) — argues that forcing depth into a single class violates the Single Responsibility Principle; the Java I/O example conflates file reading with buffering, two genuinely separate concerns; deep general-purpose classes also resist composition and create coupling that contradicts domain-driven design practice
-
"Modules Should Be Deep!" (Software Engineering: A Modern Approach) — adds nuance:
String.isEmpty()provides semantic clarity and safe encapsulation despite its trivial implementation, challenging a purely quantitative interpretation of depth; Bertrand Meyer’s iceberg metaphor — small visible tip over large submerged base — is cited as a complementary framing