perspectives

Abstraction Is Not How We Hide Things

0:00 / 0:00

The conventional wisdom about abstraction is wrong. Abstraction isn't about hiding complexity. It's about showing the path from problem to solution, with controlled loss at each translation.

5 min read
Yurii Rashkovskii
Yurii Rashkovskii
Founder

The conventional wisdom about abstraction is wrong.

We’re taught that abstraction means hiding complexity. Tuck implementation details behind interfaces. Shield consumers from the messy reality underneath. Information hiding. Encapsulation. The less you see, the better.

But this framing misses what abstraction actually does.

Abstraction isn’t about hiding. It’s about showing. Specifically: showing the path from a problem to a solution, decomposed into steps that translate across domains.

The Translation View

Every abstraction is a translation. You have a concept in one domain and you translate it into another domain. Each translation crosses a boundary between ways of thinking about the world.

Consider what happens when a user saves a document. The concept “save my work” gets translated through layers:

  • Application logic: “persist this document state”
  • File system call: “write these bytes to this path”
  • Device driver: “issue these block writes”
  • Hardware interface: “flip these bits on this sector”

Each layer speaks a different language. Meaning crosses from one domain to another, and something is always lost in transit.

The power of abstraction isn’t in making things invisible. It’s in making the translation explicit and controllable.

What Gets Lost

Every translation loses something. When you move from business requirements to code, nuance evaporates. When you move from code to machine instructions, intent disappears. This is unavoidable: it’s the nature of crossing between domains with different expressive capabilities.

The question isn’t whether you lose information. The question is whether you control what gets lost.

Good abstractions preserve what matters for the next layer while shedding what doesn’t. Bad abstractions discard essential meaning while preserving irrelevant detail. Each layer has what you might call a loss function: a characteristic pattern of what it keeps and what it drops. The art is in choosing what survives the crossing.

Layering and Value

Abstraction is inherently layered. You pass through intermediate representations rather than jumping directly from intent to implementation. Each layer is a translation, and each discards something.

Here’s the insight: the less loss between layers, the more value each translation can provide. When layers communicate cleanly, when the output of one is well-matched to the input of the next, the overall system preserves more of the original intent.

Conversely, when there’s impedance mismatch between layers, when you have to contort concepts to fit the next layer’s expectations, you lose meaning. The signal degrades faster than it should.

The Direct Path Problem

Consider what happens when you skip layers. Going directly from business requirements to infrastructure code, for instance.

This is tempting. Fewer translations means fewer opportunities for loss, right? Just implement what the business needs, directly.

But the loss doesn’t disappear. It concentrates. Without intermediate abstractions to guide the translation, you make all the decisions at once, and you make them implicitly rather than explicitly. The business logic becomes entangled with infrastructure concerns. The translation that should have happened across multiple controlled steps happens in one uncontrolled leap.

The result: a system where you can’t see the path from problem to solution. Where changing business requirements means rewriting infrastructure. Where what got lost is invisible and therefore unmanageable.

The layers aren’t overhead. They’re how you control the translation.

What Makes a Good Abstraction

This reframing has practical implications:

Layer boundaries become meaningful. They’re not arbitrary lines where you decided to stop showing things. They’re points where the domain changes, where a different way of thinking becomes appropriate.

Leaky abstractions are mistranslations. When an abstraction “leaks,” it’s not that the hiding failed. It’s that the translation failed to preserve something the next layer needed. The fix isn’t better hiding. It’s better understanding of what needs to cross the boundary.

Documentation changes. Instead of documenting what the abstraction hides, you document what it translates. What domain does it operate in? What does it receive? What does it produce? What gets lost in the translation, and why is that acceptable?

Examples of Translation

The pattern appears everywhere once you look for it.

SQL and query plans. SQL captures intent: “give me rows where this condition holds.” It’s a representation, not a translation. The query planner does the translation work, converting declarative intent into an execution plan: which indexes to use, what order to join tables, whether to scan or seek. The plan then translates into actual I/O operations. Each step discards something. The planner discards your phrasing. The executor discards the plan’s structure. What survives is the result set you asked for.

Type systems and compilation. You write user: User and the type checker translates that annotation into constraints that propagate through your program. At compile time, those constraints become machine code that assumes the shape of data without checking. The runtime flexibility is gone - you gave it up - but so are the defensive checks you’d otherwise write. The type system didn’t hide the constraint. It transformed it from something you enforce into something the compiler enforces.

Rules and inference. You write “when these conditions hold, conclude this.” The rule engine translates that into a network of dependencies, watching for facts that match. When facts arrive, the engine decides which rules fire and in what order. Your declarative statement becomes operational behavior. The execution order never existed in your rule - the engine invented it. What survives is the logical relationship between conditions and conclusions.

The Uncomfortable Parts

This view suggests that much of what we call “clean architecture” is actually about controlling what gets discarded across domain translations, not about hiding implementation details.

But here’s what’s uncomfortable: the first translation is often the hardest.

Going from business intent to any formal representation loses nuance. The person who wants to “save their work” has context, expectations, and edge cases in their head that don’t survive translation into code, rules, queries, or any other formal notation. The formal representation is always a lossy compression of human intent.

Different mental models make this harder. Each layer requires thinking in a different way. The SQL layer thinks in sets and relations. The rules layer thinks in conditions and conclusions. The imperative layer thinks in sequences and state. Crossing these boundaries isn’t just translation. It’s context-switching between entire modes of thought.

Emergent behavior from composed abstractions can surprise. When multiple translations compose, the resulting behavior is the product of all their individual choices interacting. Something preserved by one layer might be discarded by the next. Something that seemed irrelevant becomes essential when viewed from a different angle. The whole is harder to predict than the sum of the parts.

These difficulties don’t disappear with better abstractions. They’re inherent to translation between domains.

The Question

The details aren’t hidden. They’re transformed. And the quality of an abstraction is the quality of that transformation.

Which raises harder questions: where do we lose the most signal? The first translation, from human intent to formal representation, seems like the bottleneck. Every subsequent layer can only work with what survived that initial compression.

How do we make that first translation less lossy? We don’t have all the answers yet.


Share