Abstraction
Abstraction is deciding what matters. Given a messy reality, you keep the details relevant to your purpose and deliberately hide the rest behind a simpler representation. Wing called it the “mental tool” at the heart of computational thinking, and it is the pillar that separates people who can build small things from people who can build systems.
A subway map is the canonical example: it discards geography, distance, and street names, keeping only stations and connections — because the rider’s question is “how do I get from A to B,” not “where exactly am I.” The same city needs a different abstraction for the water department. An abstraction is only good or bad relative to a purpose.
Layers: abstraction stacked on abstraction
Computing is a tower of abstractions, each layer usable without understanding the one below: transistors → logic gates → machine code → operating system → runtime → library → your application → the business concept it implements. You write db.save(order) and, in that moment, do not think about B-trees, disk sectors, or voltage levels. This is not ignorance; it is engineered ignorance, and it is the only reason software of any size can exist.
Two disciplines make layering work:
- Each layer speaks only to its neighbors. When application code reaches down three layers to fiddle with something low-level, you have created a fragile shortcut that every future change must tiptoe around.
- Each layer has one vocabulary. A layer that mixes business terms with byte-shuffling terms is two layers wearing one trench coat — split it.
Interfaces and information hiding
An interface is the visible face of an abstraction: the set of operations you may perform and the promises attached to them. Everything else is implementation — private, changeable, nobody’s business. David Parnas formalized this in 1972 as information hiding: modules should hide design decisions likely to change, so that changing them later breaks nothing outside.
The practical power is the contract. If the interface promises “sort(list) returns the items in ascending order,” callers can rely on that promise while the implementation swaps quicksort for timsort overnight. Interfaces decouple what from how, which decouples teams from each other, which is why decomposition (see Decomposition) only pays off when the seams between parts are proper interfaces rather than shared internals.
A useful habit when designing any interface:
- Write the operations from the caller’s point of view, in the caller’s vocabulary.
- State each promise explicitly — inputs accepted, outputs guaranteed, errors possible.
- Ask: “if I completely rewrote the inside, would any caller notice?” If yes, an implementation detail has leaked into the contract.
Leaky abstractions
Joel Spolsky’s Law of Leaky Abstractions states: all non-trivial abstractions, to some degree, leak. The hidden details are still there, and under some circumstance they surface.
Classic leaks:
- A network filesystem presents remote files as if local — until latency, timeouts, and partition failures remind you the network exists.
- An ORM presents rows as objects — until an innocent-looking loop issues 10,000 queries.
- Garbage collection hides memory management — until a pause spike hits your latency budget.
- Floating-point numbers act like real numbers — until
0.1 + 0.2fails an equality check.
This is not an argument against abstraction; it is an argument for choosing abstractions whose failure modes you can afford, and for keeping someone on the team who knows what is under the floorboards. LLMs, incidentally, are the leakiest abstraction in current use — “ask in English, receive an answer” hides a statistical machine whose leaks are called hallucinations. Model Thinking is the one-layer-down knowledge for that abstraction.
Worked example: a parking garage occupancy sign
Problem: the city wants signs showing available spaces per garage, fed by entry/exit sensors.
Choose what to keep
The purpose is “help drivers decide where to head.” Relevant: count of free spaces per garage, staleness of that count. Irrelevant (for this purpose): which specific spaces are free, license plates, payment state, sensor hardware brand. The entire abstraction is one number and one timestamp per garage.
Define the interface
One operation for producers: record_event(garage_id, direction, timestamp) where direction is enter or exit. One for consumers: get_availability(garage_id) returning {free_count, as_of}. Promises: availability is never negative, never exceeds capacity, and as_of is at most 60 seconds old. Note the interface says nothing about sensors, databases, or counting logic — all hidden, all replaceable.
Layer it
Sensor layer (hardware pulses → clean events), aggregation layer (events → running count), publication layer (count → signs and a public API). The city can later swap ultrasonic sensors for cameras, touching only the bottom layer.
Anticipate the leaks
Sensors miscount: two cars tailgating register as one. The clean abstraction “count = entries minus exits” drifts from truth by a few spaces per day. The leak is handled inside the abstraction — a nightly recalibration from a manual or camera count — and acknowledged in the contract by documenting accuracy as ±5 spaces. Drivers get “about 42 free,” which is exactly the precision their decision needs.
The design lesson: the sign never lies about what it knows because the abstraction was scoped to what the sensors can actually support. Good abstraction is honest simplification; bad abstraction is wishful thinking with an API.