EMZETT.
Login

Abstraction

In short: The principle of hiding complex internal details behind a simple, clearly defined interface — you need to know WHAT something does, not HOW it does it in detail.

In more detail: A car driver doesn’t need to know how the engine works internally to drive — the accelerator and steering wheel are enough as an interface. Likewise, an interface or abstract class defines WHICH methods exist, without specifying their concrete implementation — that’s left to the implementing classes. Abstraction is one of the four core principles of OOP.

In Depth

Abstraction shows up at several levels at once, and it’s worth telling them apart:

  • Data abstraction: A data type like Stack offers push()/pop() without revealing whether an array or a linked list is used internally.
  • Control abstraction: A function sort(list) hides which concrete sorting algorithm is behind it.
  • Interface abstraction: An interface or an abstract class only specifies WHICH methods must exist — not how they’re implemented.
  • Abstraction levels at large: An entire system can be thought of in layers (database layer, business logic layer, presentation layer) — each layer abstracts the one beneath it and offers the one above it a simplified view.

A simple Python example of control abstraction:

def calculate_total_price(cart):
    # the caller doesn't need to know how discounts/taxes are calculated internally
    subtotal = sum_items(cart)
    return apply_tax_and_discount(subtotal)

The same idea can be expressed with an interface in TypeScript at the type level — the caller only knows the shape, not the implementation:

interface PaymentProvider {
  charge(amount: number): Promise<boolean>;
}
 
// Two completely different implementations, interchangeable behind the same abstraction
class StripePayment implements PaymentProvider { /* ... */ }
class PaypalPayment implements PaymentProvider { /* ... */ }
 
function checkout(provider: PaymentProvider, amount: number) {
  // This function doesn't care at all WHICH provider is behind it
  return provider.charge(amount);
}

The big advantage: if the internal implementation changes (e.g. a more efficient algorithm, a different data structure, a new payment provider), nobody using the abstraction has to adjust their own code — as long as the interface stays the same. This lowers coupling between parts of a program and makes large codebases maintainable, because you don’t have to keep the entire system in your head while working on one module. In software architecture, this is often called “dependency inversion”: higher levels depend on an abstraction, not on a concrete implementation.

When abstraction “leaks”

An abstraction is never a hundred percent perfect — the term “leaky abstraction” describes cases where implementation details still leak through and affect the user of the abstraction. A database ORM largely abstracts away SQL, but an inefficient N+1 query pattern (see OOP-adjacent data access patterns) is still noticeable as a real performance problem — the abstraction “SQL is no longer visible” doesn’t quite hold up against reality. This isn’t a justification for skipping abstraction, but a reminder that you should never completely ignore the underlying system just because there’s a nice interface in front of it.

Too much of a good thing

The typical pitfall is too much abstraction (so-called “over-engineering” or “speculative generality”): building an interface, a plugin system, or configurability for a use case that doesn’t (yet) even exist makes code more complicated instead of simpler — every additional layer of abstraction is also an additional place where you have to think and jump around while reading the code. Rule of thumb (also known as the “rule of three”): implement concretely first, then abstract once at least a second, real use case shows up — not already at the first, purely hypothetical one.

Distinction from encapsulation: encapsulation protects data from uncontrolled access (the HOW stays hidden because direct access is forbidden), abstraction reduces complexity by not showing details in the first place (the HOW is irrelevant for use). Both usually work hand in hand, but are independent concepts — you can abstract something without encapsulating it (a public interface with no access protection on the internals) and vice versa.

See also: Interface, OOP, Encapsulation