Glossary

Abstraction

Abstraction is the principle of hiding implementation complexity behind a simple interface, allowing users of your code to work at a higher level without understanding the underlying details.

Explanation

Abstraction operates in layers. You write fetch('/api/users'), not raw TCP socket code, HTTP header construction, and DNS resolution. fetch abstracts away all of that. Array.prototype.sort() abstracts away sorting algorithm details. Your database ORM abstracts away SQL queries. Each layer hides the complexity of the layer below it, letting you think at the appropriate level of detail for your task. In OOP, abstraction is implemented through public interfaces: a BankAccount class's public methods (deposit, withdraw, getBalance) are the abstraction; the internal state management is the hidden detail. Users of BankAccount don't need to know how transactions are recorded — they just call deposit(). In functional programming, abstraction is achieved through higher-order functions and composition: Array.map() abstracts the pattern of transforming each element without you writing the loop. Abstract classes (a concept from typed OOP languages like Java, TypeScript) define interfaces that subclasses must implement. They can have both abstract methods (no implementation — subclasses must implement) and concrete methods (shared implementations). TypeScript supports abstract classes; pure JavaScript doesn't, but the convention of throwing new Error('Not implemented') in base class methods achieves similar enforcement. Leaky abstractions: all non-trivial abstractions have "leaks" — situations where the underlying implementation details bleed through. React is an abstraction over DOM updates, but you still need to understand keys, refs, and the reconciliation algorithm to fix certain bugs. TCP is an abstraction over IP routing, but network partitions break the abstraction. Good engineers know when to drop through abstraction layers.

Code Example

javascript
// Abstraction in practice: repository pattern

// Without abstraction: implementation details everywhere
async function getUser(id) {
  const result = await db.query('SELECT * FROM users WHERE id = $1', [id]);
  return result.rows[0];
}

// With abstraction: UserRepository hides DB details
class UserRepository {
  async findById(id) {
    const result = await db.query('SELECT * FROM users WHERE id = $1', [id]);
    return result.rows[0] ? this.#toEntity(result.rows[0]) : null;
  }

  async findByEmail(email) {
    const result = await db.query('SELECT * FROM users WHERE email = $1', [email]);
    return result.rows[0] ? this.#toEntity(result.rows[0]) : null;
  }

  async save(user) {
    await db.query(
      'INSERT INTO users (id,name,email) VALUES ($1,$2,$3) ON CONFLICT (id) DO UPDATE SET name=$2,email=$3',
      [user.id, user.name, user.email]
    );
  }

  #toEntity(row) {
    return new User(row.id, row.name, row.email); // map DB row to domain entity
  }
}

// Caller works at business logic level — no SQL
const userRepo = new UserRepository();
const user = await userRepo.findById(42);
// If we switch from PostgreSQL to MongoDB, only UserRepository changes

Why It Matters for Engineers

Abstraction is what makes large systems manageable. Without it, every change requires understanding every layer simultaneously. With it, you can work on the database layer without knowing the UI, and on the UI without knowing the database. The repository pattern, service layer, and API client wrappers are all abstractions that create this separation. Recognizing when an abstraction is leaking — when using it correctly requires knowing its implementation details — is a critical engineering skill. Leaky abstractions are often the source of mysterious bugs and performance problems that "shouldn't be happening" given the abstraction's claimed behavior.

Related Terms

Encapsulation · Interface · Class · Polymorphism

Learn This In Practice

Go deeper with the full module on Beyond Vibe Code.

Programming Foundations → →