Back to the map

Module 22 · OOP in practice · Lesson 3 of 4

Interfaces vs abstract classes

Two ways to define a type others implement. What each can hold, single versus multiple inheritance, default methods and how they blur the line, how each evolves when you add a method, and a short rule for choosing.

CoreMust-knowMid 11 min Updated Nov 2026

Why it matters

"When would you use an abstract class instead of an interface?" is one of the most common object-oriented interview questions, and the textbook answer from 2005 is out of date. Default methods in Java and C# have blurred the line, and Go, Rust and TypeScript use different models entirely. What still matters is what each lets a type hold, how many a class can have, and what happens when you add a method later and a hundred implementations already exist.

The idea

Both define a type that other classes implement or extend, and both can declare methods without implementations. They differ in what else they can carry and how many a class can take.

CapabilityInterfaceAbstract class
Abstract methodsYesYes
Method implementationsDefault and static methods (Java 8+, C# 8+)Yes, any
Instance state (fields)No; constants onlyYes
ConstructorsNoYes, to initialise shared state
Non-public membersLimited (private helpers in Java 9+)protected and private, freely
How many a class can haveManyOne (in Java and C#)
ExpressesA capability or role: Comparable, Closeable, PaymentGatewayA partial implementation of a family: a base with shared state and a fixed algorithm
abstract BaseExporterstate: config · export() fixedCloseableRetryableS3ExporterwriteChunk() implementedextends (one)implementsimplements
A class has one parent but many roles. The abstract class carries shared state and a fixed algorithm; the interfaces are capabilities that any class can add.

Evolving each one

Adding an abstract method to a published interface breaks every implementer, because each must now provide it. Default methods were added to Java and C# precisely to evolve interfaces without breaking implementations: the new method ships with a body that implementations inherit until they override it. Abstract classes have always allowed this, since a new concrete method is simply inherited. The difference that remains is state: a default method can only be written in terms of the interface's other methods, while an abstract class can use fields.

The abstract class's real job

Abstract classes shine for the template method pattern: the base class fixes the steps of an algorithm and the shared state, and subclasses fill in specific steps. An exporter that always opens a destination, writes chunks with retries and closes cleanly, where only "write a chunk" varies, is a natural abstract base. The cost is the single-parent slot and the fragile-base-class coupling from the inheritance lesson.

Other languages, other models

LanguageModelEffect
GoImplicit, structural interfaces; no inheritanceSmall interfaces defined by consumers; reuse by embedding structs
RustTraits with default methods; no inheritance of dataBehaviour is composed from traits; shared data by composition
TypeScriptStructural interfaces and abstract classesInterfaces describe shapes, erased at runtime; abstract classes exist at runtime
PythonAbstract base classes and ProtocolsABCs enforce methods at instantiation; Protocols type-check structurally
Scala, KotlinTraits or interfaces with state-free defaults, sealed hierarchiesMixins of behaviour; closed hierarchies for pattern matching

The trend across modern languages is clear: behaviour comes from interfaces or traits, data and reuse come from composition, and implementation inheritance is used sparingly.

A short rule

  1. 1

    Default to an interface

    It defines the contract, leaves implementers free to extend anything else, and is easy to fake in tests.
  2. 2

    Add an abstract base class alongside it when implementations share real state or a fixed algorithm

    Many libraries ship both: an interface for the contract and an optional abstract skeleton that implements the boring parts.
  3. 3

    Use default methods for convenience and evolution, not to smuggle in behaviour that needs state

    If a default needs fields, it belongs in a class.
  4. 4

    Seal what you do not intend others to extend

    Sealed interfaces or classes, or final classes, make the hierarchy closed and the design explicit.

Trade-offs

Interfaces give flexibility, multiple roles and easy testing, at the cost of no shared state. Abstract classes give shared state, constructors and a fixed skeleton, at the cost of using up the only parent and coupling subclasses to the base. Default methods narrow the gap and add their own risk: two interfaces with the same default force a class to resolve the conflict explicitly.

In an interview

Answer with both the rule and the reason: "PaymentGateway is an interface because it is a role with several unrelated implementations and we fake it in tests. Underneath, an abstract HttpGateway base holds the HTTP client, retry policy and signing, and each provider extends it and implements only the request mapping. If we need to add a method to the interface later, it ships as a default to avoid breaking third-party implementations."

Check yourself

A library adds a new abstract method to a public interface that thousands of external classes implement. Their builds break on upgrade. What would have avoided this?

Interfaces vs abstract classes · Backplane