CS PAPERS DEEP-READ · PAPER 43
David Parnas · Carnegie Mellon · CACM 1972
Splitting a big program into pieces (modules) is a near-universal step in building software. But in 1972, David Parnas pressed a question nobody had really answered: by what criterion should you make the cut? His answer, information hiding, is exactly why you can quietly swap out one module without disturbing the rest, why a large team can each build their own part, and why words like "object-oriented" and "API" exist at all.
The most natural cut is by processing step: the program reads input, then processes, then sorts, then outputs — so make one module per step, slicing along the flowchart. It sounds obvious, and almost every beginner does it.
The catch is that these "by-step" modules all share an unspoken secret: how the data is laid out in memory (how a line is stored, how a word sits). The day you want a more memory-efficient layout, you find nearly every module has to change too — because each one reaches directly into that data format. Pull one thread and the whole thing moves; this is the root of why software is hard to change and hard to divide up.
He said: don't cut by step — cut by "secret." First sit down and list which design decisions are most likely to change later (how data is stored, which sort algorithm, what format the external data has…). Then seal each such decision inside its own module, known only to that module. Any other module that wants to use it can only deal with it through a "window" (a set of agreed-upon functions), and can never see how it's implemented behind that window.
Think of a module as a restaurant kitchen. You (the other modules) only order and pick up at the service window; what pans they use, what recipe they follow, how they plate it, is the kitchen's secret, kept behind closed doors. If the kitchen swaps its stove or changes its method, as long as the same dish still comes out the window, you never need to know, and don't change a single line. The change is sealed in one room and can't leak out — and everyone can run their own room in parallel, needing only to agree in advance on what the windows look like.
Don't split modules by "the program's processing steps" — split by "the design decisions most likely to change." Each module hides one such "secret" and exposes only a stable window (an interface) to the rest. Change stays contained, and teams can each build their own part. This criterion grew into encapsulation, abstract data types, object-oriented programming, and the API.
(One honest cost: you have to guess right about "what will change." Guess wrong and you've hidden things that never change — wasted effort — or over-engineered for a change that never comes.)
Want the side-by-side of the two decompositions, the KWIC example, and how it grew into OOP? → switch to the deep read
Parnas argues that a system should be split into modules not by "processing steps / the flowchart," but by information hiding: first list the design decisions most likely to change, then have each module hide one such "secret," dealing with the rest only through an interface that exposes as little of its innards as possible. Then change is confined to a single module, teams can develop independently, and each module can be understood on its own. This short paper is the source of encapsulation, abstract data types, object-oriented programming, and modern API / microservice boundaries.
The author is David Parnas, writing at Carnegie Mellon University, published in the December 1972 Communications of the ACM (CACM). It follows the 1960s discussions of "structured programming" and "modularity" (Dijkstra's Go To Considered Harmful is part of the same wave), but turns "modularity" from a slogan into an actionable criterion; it launches the 1970s abstract data types (Liskov et al.), 1980s object orientation, and Parnas's own later ideas of the "uses hierarchy" (1979) and "program families." It is routinely listed among the most cited, most influential papers in software engineering.
By the early 1970s, "split a big program into modules" was accepted as good — it lets you divide labor, compile and debug pieces separately, and replace them. But "modularity" as then discussed stopped at "cut it into pieces," with almost no answer to "cut it by what criterion." Parnas saw sharply that the criterion makes all the difference: same "split into modules," yet one split makes the system easy to change and divide, another makes it fight you at every turn.
He uses a small system (a KWIC index) as his target, lays out two decompositions that both "look reasonable," and compares them point by point on changeability, independent development, and comprehensibility — forcing out the criterion you should actually use. What he argues against is the most popular, most "natural" split of the day: cutting along the flowchart's processing steps.
The task is simple: given a set of text lines, output all their circular shifts in alphabetical order. "the quick fox" yields "quick fox the," "fox the quick," and so on; all shifts of all lines are merged and sorted for output. Tiny, but enough to contrast two decompositions.
Following the flow of data, one module per step: ①Input (read and store in memory) → ②Circular Shift (generate all shifts) → ③Alphabetize → ④Output → plus a ⑤Master Control to sequence them. Clean, intuitive, one-to-one with the flowchart.
But there's a hidden hazard: all five read and write the same in-memory data, and all must know "exactly how lines, words, and characters are stored." If Input decides to pack characters (say four chars per machine word to save memory), then Shift, Alphabetize, and Output all must read in that format. The storage decision is shared across five modules.
Parnas's alternative starts from a completely different place — not the flow: first list the "difficult, or most-likely-to-change design decisions," and seal each one inside its own module, hidden from the rest. The same KWIC then splits into:
get char k of word j of line i — callers fetch a character without knowing or caring whether it's packed or one-per-cell.The key difference in one line: Decomposition 1's modules connect by "sharing a data structure," their interfaces exposing the internal representation; Decomposition 2's modules connect only by "a set of functions," their interfaces revealing as little as possible about how the work is done. Each module is a black box; all that's visible from outside is the window.
① Changeability. Want a new storage format? In Decomposition 1 all four modules must move; in Decomposition 2 the change falls only inside "Line Storage," the interface unchanged, and no one else notices. Likewise, changing "precompute all shifts" to "compute on demand" to save memory touches only the Circular Shifter. Separating "the decision that will change" from "the code that depends on it" is the whole point of the criterion.
② Independent development. In Decomposition 2, modules need only agree in advance on the interface (what the functions look like and how they behave), after which each team builds its implementation behind closed doors, barely needing to consult the others. In Decomposition 1 everyone shares data structures, so any internal change disturbs the rest — coordination is costly.
③ Comprehensibility. In Decomposition 2 each module can be read and verified on its own, because its behavior is fully described by its interface, without first understanding another module's internals.
Parnas underlines two points, both counterintuitive. First, a "module" is not "a step on the flowchart" but "an assignment of responsibility" — of "who keeps which secret." Second, what should be hidden is often not "trivial details" but precisely the "hard, or sooner-or-later-changing core design decisions"; the interface should be designed to expose as little as possible — the less it reveals, the more freedom you keep later.
This is an argumentative (not experimental) paper; its "result" is that comparison table: for the same KWIC system, the two decompositions rank starkly differently on changeability, division of labor, and comprehensibility — the by-step version lets one storage change ripple through every module, while the by-secret version confines the same change to a single module. The conclusion lands hard: what determines whether a modularization is good is not "whether you split," but "by what criterion" — and the right criterion is information hiding, not the flowchart. He also addresses the efficiency worry head-on: hiding an implementation seems to cost an extra function-call layer, but an interface is a design agreement, not a mandate to implement it via slow subroutine calls — you can keep the hiding and still be efficient.
This short paper turned "modularity" from a slogan into a methodology, and all but defined half a century of thinking about software structure:
① In one line: don't split modules by "processing steps / the flowchart," split by "information hiding" — each module hides one "most-likely-to-change design decision" and exposes only a stable interface.
② The pain: by-step modules share one data format, so a single storage change ripples through all of them — hard to change, hard to divide.
③ Core: first list the "hard / changeable design decisions," seal each inside a module as its "secret," and let others use it only through a set of functions (the interface), never seeing the implementation.
④ Three payoffs: changeability (change stays in the box), independent development (only agree on interfaces), comprehensibility (each module understandable alone).
⑤ Two clarifications: a module is a "responsibility assignment," not "a flowchart step"; what to hide is the core hard problem, and the interface should expose as little as possible.
⑥ Method of argument: contrast two decompositions of a KWIC index point by point on change/labor/understanding — by-secret wins decisively.
⑦ Impact: encapsulation, abstract data types, the OOP class, API / microservice boundaries are all descendants of this criterion.
⑧ Limits: success rides on "guessing what will change"; layered abstraction has costs and invites over-design; abstractions leak; it gives a criterion but not the whole structure (later filled by the uses hierarchy / program families).