Deep Module
also called Narrow Interface Wide Behaviour, Shallow Module Counterpart
A module whose simple interface hides a lot of behaviour, so callers do little work to use it; its opposite, the shallow module, exposes nearly as much as it contains.
A team wraps an HTTP client in a class with twelve methods, one for each thing the client already does. Callers must now learn the wrapper and the client behind it. A deep module reverses this: the interface is small and the work behind it is large, so each caller learns little and gains much.
Why it matters
Complexity in software is largely what a person must keep in mind to change it safely. John Ousterhout, in A Philosophy of Software Design (2018), argues that the value of a module is the functionality it provides minus the interface the caller must learn. A module with a tiny interface and a big implementation lowers the total load across all callers. A module whose interface mirrors its implementation adds a layer to read and nothing to the caller.
Implementation patterns
- Measure the ratio: count public methods and parameters against internal behaviour. A rule of thumb: an interface under about a fifth of the size of the behaviour is a good sign.
- Make the common case one call:
save(document)handles retries, serialization and conflict detection inside. - Hide decisions: choose defaults inside (buffer sizes, retry counts) instead of exposing every option; expose an option only when callers differ in what they need.
- Pull complexity down: when a rule would otherwise be repeated by every caller, move it into the module even if that makes the module harder to write.
- Merge shallow neighbours: two classes that always change together and whose interfaces equal their contents can become one deeper class.
- Classic example: Unix file input and output offers about five calls (open, read, write, seek, close) over disk layout, caching, permissions and device drivers.
Industry example
Ousterhout's own comparison is the Unix file interface against typical class-heavy libraries, in the 2018 book. No company claim is needed. As an archetype, a payments team replaced fourteen public methods around card handling with a single charge(order) that decides retries, idempotency keys and fraud-check ordering internally. Callers shrank from forty lines to three, and a change to retry policy touched one file.
Failure scenarios
- Leaky depth: the module hides so much that callers cannot influence cost, such as a "get orders" call that runs 240 queries.
- Over-merging: one huge class that mixes concerns because depth was pursued without cohesion.
- Shallow wrappers: a layer of pass-through methods adds hops in the stack trace and reading time.
- Hidden options needed later: a default that is wrong for one caller forces a breaking change.
- Depth by configuration: a hundred flags is a wide interface in disguise.
Trade-offs
| Choose | Gains | Pays |
|---|---|---|
| Deep module | Small learning cost per caller | Harder to write; wrong defaults affect all callers |
| Shallow, flexible module | Callers control everything | Each caller repeats the knowledge |
| Pass-through wrapper | Swappable dependency | An extra layer that hides nothing |
When not to use it
Do not chase depth where callers truly differ in needs, such as a low-level library meant for expert users who must tune buffers. Also avoid it where the hidden decision is expensive and varies: if a call can cost 1 ms or 10 s depending on input, callers need to see that. A thin wrapper for a provider migration that may never happen is a cost today for an uncertain payoff.
Interview question
Q: Review a client library with 40 public methods that each map to one backend endpoint. What do you change?
What a strong answer covers: group by caller task, not endpoint; design the three or four calls most callers need; push retry, pagination and error mapping inside; keep an escape hatch for rare cases; measure caller code before and after; and say what you would not hide (cost, failure modes).
Quick check
Quiz: What is the sign that an abstraction has become a liability? Callers must understand the hidden part anyway to use it correctly.
Flashcard: What makes a module deep? — A small interface in front of a large amount of behaviour.