Intermediate12 min readObject-Oriented Foundationslive prototype

Abstract Classes vs Interfaces

An abstract class is a half-built parent you extend — it can hand down real code and stored data, but you get only one. An interface is a checklist of abilities you implement — it carries nothing, but you can stack as many as you like.

The idea

What it is

Both an abstract class and an interface let you say "any object of this type will have these methods" without writing all the code yet. They look so similar that people mix them up. Here's the real split: an abstract class is a parent you inherit from — and it can hand its children real, working code plus stored data. An interface is just a labelled checklist of abilities — it hands down nothing; whatever takes it on must write every method itself.

Think of building a smart device. An abstract Device is like a half-finished product coming off the line — the battery and the charging circuit are already installed (that's shared data and working code), but the "what happens when you switch it on" part is left blank for each specific product to finish. You can't sell the half-finished unit itself; you complete it into a real SmartCamera or SmartBulb. An interface like Recordable is just a spec sticker that says "this can record" — it installs nothing, so whatever wears the sticker has to wire up record() on its own. A product is built on one base, but it can wear many stickers.

The one sentence to remember

If the relationship is "is a kind of" and you want to share code and data → abstract class. If it's just "can do this" and unrelated types need the same ability → interface. is-a → abstract class; can-do → interface.

Mechanics

How it works

An abstract class — a half-built parent

An abstract class is a normal class with one twist: you can't create an object from it directly — it's unfinished. But it can carry almost everything a real class carries, and it hands all of it down to its children:

  • Stored data (fields) — like a battery level — that every child inherits automatically.
  • A constructor — setup code that runs when a child object is built.
  • Fully-written methods — real code, like charge(), that every child gets for free.
  • Blank abstract methods — a method name with no body, which each child is forced to fill in.

A child extends the base and is a kind of it. The one big limit: a class can extend only one base — just as a real product sits on a single chassis. That single inheritance is what keeps the shared-data story simple.

device.ts
// Can't do `new Device()` — it's half-built on purpose.
abstract class Device {
  protected battery = 0;            // stored data — every child inherits it

  charge(pct: number): void {      // real, working code — children get it free
    this.battery = Math.min(100, this.battery + pct);
  }

  abstract turnOn(): string;       // blank — each child MUST fill this in
}

An interface — a checklist of an ability

An interface is leaner: classically it's just method names, no bodies and no stored data. A class implements it by writing the real code for every method it lists. The superpower is that a single class can implement many interfaces at once — stacking abilities that come from completely unrelated places.

abilities.ts
// Interface = the promise of an ability. No data, no code — just the list.
interface Recordable { record(): string; }
interface Connectable { connectWifi(): void; }

// One class can wear MANY stickers at once:
class SmartCamera extends Device implements Recordable, Connectable {
  turnOn()      { return "camera live"; }   // fills Device's blank
  record()      { return "recording 4K…"; } // fulfils Recordable
  connectWifi() { /* … */ }                 // fulfils Connectable
}

Most real classes use both

These aren't rivals — the everyday answer is to use them together. Extend one abstract base for the shared data and code, and implement several interfaces for the extra abilities. SmartCamera is a Device (so it gets battery and charge() for free) and can do Recordable and Connectable (so it promises those abilities and writes them itself). Base for what they are; interfaces for what they can do.

A quick note on your language

Modern languages blur the line a little, but the core stays: an interface still holds no stored data, and you can still take on many. In Java, interfaces can add default method bodies (Java 8+) yet keep no instance fields. TypeScript interfaces just describe a shape and vanish at runtime. Python uses abc.ABC for abstract classes and Protocol for interface-style contracts. C++ has no interface keyword — there an interface is simply a class whose methods are all pure virtual (= 0).

Interactive prototype

See it. Build it. Break it.

A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.

About this simulation

Build a smart device, piece by piece. First pick one foundation to extend — Device (battery-powered) or Appliance (wall-powered) — and watch its Inherited for free panel fill with working code you didn't write. Then check any number of capability badges (Recordable, Dimmable, …); each drops its methods into the You must implement panel. Press Run it to see the inherited code run on its own while your own methods fire alongside. Try clicking a second foundation — it tells you a class gets only one.

Hands-on

Try these yourself

Open the prototype above, predict what happens, then verify.

try 01

Pick a foundation — you only get one

Under Foundation — extends (pick 1), click Device. The class card's Inherited for free panel fills with battery, charge(), and batteryLevel() — code you now have without writing a line. Now click Appliance: the log tells you a class can extend only one base, so it replaces Device. That swap is single inheritance you can feel.

try 02

Stack as many capabilities as you want

Under Capabilities — implement (pick any), check Recordable, then MotionSensor, then Connectable. Each drops its method into the amber You must implement panel, and the Capabilities ∞ counter keeps climbing. Interfaces stack freely — the exact opposite of the one-and-only base.

try 03

Build it and run it

With a foundation and a few badges selected, press Run it. In the console, the green lines are inherited code running on its own (you wrote zero lines of charge()), while the accent and blue lines are the methods you had to supply — the base's blank turnOn() and each capability. Hit Reset to build a different device.

In practice

When to use it — and what trips people up

A simple way to choose

Start with an interface — it's the lighter, more flexible choice, and types that share nothing but an ability can still take it on. Reach for an abstract class the moment you want to share real code or stored data across children that genuinely form an is-a family (a SmartCamera and a SmartBulb really are both a kind of Device). And when both pulls are real at once, don't pick — use an abstract base for the shared part and layer interfaces on top for the extra abilities. That last combo is what most real designs land on.

Don't fake an is-a just to share one method

If you reach for an abstract base only to reuse a single helper, you drag every child into one inheritance line they might not belong in. If the types aren't truly the same kind of thing, an interface (or composition) keeps them free and untangled.

What it gives you

  • Reach for an abstract class when children share a real is-a relationship — a SmartCamera and a SmartBulb are both genuinely a kind of Device.
  • Reach for an abstract class when there's concrete code or stored data every child should inherit, like a battery field and a working charge() method.
  • Reach for an abstract class when you want a constructor to run the same valid setup for the whole family of children.
  • Reach for an abstract class when one base is enough and you want a single, central place to evolve shared behaviour over time.

Common mistakes

  • Reach for an interface when unrelated types need the same ability — a Camera and a Phone can both be Recordable without sharing any family.
  • Reach for an interface when one class must mix several abilities at once, since a class can implement many but extend only one base.
  • Reach for an interface when you want a pure contract with no data — just method names callers can rely on, each type writing its own version.
  • Reach for an interface for flexibility and easy testing — any type can opt into a capability and be swapped for a mock, with no inheritance baggage.

Reference

Code & further reading

A minimal reference implementation and pointers worth bookmarking.

// Abstract class: a half-built PARENT — stored data + real code,
// plus a blank method each child must finish. Can't be `new`-ed directly.
abstract class Device {
  protected battery = 0;                 // stored data every child inherits

  charge(pct: number): void {            // real code — children get it FREE
    this.battery = Math.min(100, this.battery + pct);
  }

  batteryLevel(): string {               // real code — shared by all devices
    return `${this.battery}% battery`;
  }

  abstract turnOn(): string;             // BLANK — each child fills this in
}

// Interface: just the promise of an ability. No data, no code.
interface Recordable {
  record(): string;
}

// SmartCamera IS-A Device (extends ONE base) and CAN record (implements an interface).
class SmartCamera extends Device implements Recordable {
  turnOn(): string {                     // fills Device's blank
    return "camera live";
  }
  record(): string {                     // fulfils the Recordable promise
    return "recording 4K…";
  }
}

const cam = new SmartCamera();
cam.charge(30);                  // inherited code runs — we never wrote charge()
console.log(cam.batteryLevel()); // "30% battery"
console.log(cam.turnOn());       // "camera live"
console.log(cam.record());       // "recording 4K…"

References & further reading

6 sources

Knowledge check

Did it land?

Quick questions, answers revealed on submit. Sign in to save your best score.

question 01 / 05

Which capability belongs to an abstract class but NOT to a classic interface?

question 02 / 05

A SmartCamera needs to be recordable, motion-sensing, and Wi-Fi connectable — three unrelated abilities. What's the natural fit?

question 03 / 05

Since Java 8, interfaces can include default methods with real bodies. Does that make them the same as abstract classes?

question 04 / 05

You need children to share a stored battery field AND mix in several unrelated abilities. What's the idiomatic design?

question 05 / 05

Why can't you write new Device() when Device is an abstract class?

0/5 answered