skip to content

Given a real modeling scenario, how do you decide between an interface, an abstract class, or both — what concrete signals push you each way?

level: middleimportance: must knowfreq 68%

answer

  1. capability/role -> interface; is-a kind -> abstract class
  2. already extends something / heterogeneous -> interface
  3. shared per-instance state or constructor -> abstract class
  4. want both? interface + abstract skeletal class
  5. tie-breakers: multiple roles->interface, shared state->abstract

basics

~20 s

Ask: is this a capability many unrelated types can have (use an interface), or a family of related types that share data and code (use an abstract class)? If types are unrelated or already extend something, choose an interface. If they're a real is-a family with shared fields, choose an abstract class — or expose an interface and back it with an abstract base.

solid answer

~60 s

Drive the decision from concrete signals. Use an interface when: the abstraction is a capability or role (Comparable, AutoCloseable) that semantically-unrelated types may share; implementers might already extend a different class; or you want a stable contract many providers fulfil and the freedom to add behavior later via default methods. Use an abstract class when: the subtypes form a genuine is-a family; they share per-instance state, a constructor, or substantial implementation; or you want one controlled, possibly non-public place for common code and invariants (Template Method). The signals can both fire — a contract that also wants shared code — and then the right answer is both: publish an interface and provide an abstract skeletal implementation (List/AbstractList). Two hard constraints break ties: a type that must combine several roles forces interfaces (single class inheritance), and shared mutable per-instance state forces an abstract class (interfaces are stateless). When in doubt, default to an interface for the contract and introduce an abstract class only when shared state or heavy shared implementation pays for the single-inheritance cost.

code

java · 22 lines
java
// Contract: the role
interface NotificationSender { void send(Notification n); }

// Skeleton: shared state + Template Method, for those who want reuse
abstract class AbstractRetryingSender implements NotificationSender {
    private final int maxRetries;                 // shared per-instance state
    protected AbstractRetryingSender(int maxRetries) { this.maxRetries = maxRetries; }

    public final void send(Notification n) {      // shared algorithm skeleton
        for (int i = 0; i <= maxRetries; i++) {
            if (doSend(n)) return;                // hook varies per subtype
        }
        throw new SendFailedException(n);
    }
    protected abstract boolean doSend(Notification n); // the variation point
}

class EmailSender extends AbstractRetryingSender {
    EmailSender() { super(3); }
    protected boolean doSend(Notification n) { /* ... */ return true; }
}
// A class already extending a vendor base can still: implements NotificationSender

go deeper

for a junior

Applies the basic question: capability for many types -> interface; related family sharing code -> abstract class. Can pick correctly in clear-cut cases.

for a middle

Reads multiple signals (heterogeneous implementers, existing superclass, shared state/constructor, Template Method) and recognizes the interface-plus-skeletal-class option and the two hard tie-breakers.

for a senior

Designs the split deliberately — interface as contract, abstract skeleton for reuse — and knows when composition beats inheritance; justifies in terms of coupling and future flexibility.

for a principal

Treats the modeling choice as a coupling and evolution decision across module boundaries, balancing adoptability, the fragile-base-class risk, and long-term API change, and steers teams toward interface-first with composition as the reuse default.

## Reframing the question as signals, not slogans 'Interface vs abstract class' is best answered by reading the *signals* in your domain rather than reciting rules. Below are the signals and which tool they point to, with the underlying mechanics. ### Signals that point to an INTERFACE 1. **It's a capability or role, not a kind.** If the abstraction answers *'what can this thing do?'* (be compared, be closed, be serialized) rather than *'what is this thing?'*, it's an interface. Roles cut across unrelated kinds: a `File`, a `Socket`, and a `Connection` are unrelated kinds but all `AutoCloseable`. 2. **Implementers are heterogeneous or already have a superclass.** Because a class can `implements` many interfaces but `extends` only one class, an interface doesn't consume the inheritance slot. If your candidate implementers already extend something, an interface is the only non-invasive option. 3. **You want a stable, widely-implemented contract.** Many independent providers fulfilling one small contract = interface. You can later grow it with **default methods** without breaking implementers. 4. **No shared per-instance state is needed.** Interfaces are stateless (their fields are constants), so if there's nothing to store, the lack of state is no loss. ### Signals that point to an ABSTRACT CLASS 1. **Subtypes are a true is-a family.** If the abstraction answers *'what is this?'* and the subtypes are clearly specializations of one kind (`SavingsAccount`/`CheckingAccount` are `Account`s), an abstract class models the family. 2. **Shared per-instance state or a constructor.** If every subtype carries the same fields and needs the same initialization/invariant enforcement, only an abstract class can centralize that (interfaces can't hold instance state or constructors). 3. **Substantial shared implementation.** When subtypes share a lot of real code — especially an algorithm skeleton with a few varying steps — an abstract class encodes the **Template Method** pattern: concrete `final` methods drive the flow, abstract *hook* methods are the variation points. 4. **You want controlled/non-public members.** Need `protected` helpers or to hide internals? Interfaces are all-public; abstract classes give you the full access spectrum. ### Signals that point to BOTH The contract-and-shared-code case is common: you want a public, widely-adoptable type *and* you have substantial default implementation. The idiomatic resolution is **interface + abstract skeletal class**: publish the interface as the type, and ship an `Abstract...` class implementing it with the shared code, so implementers can extend the skeleton (reuse) or implement the bare interface (freedom). The JDK does this throughout: `Collection`/`AbstractCollection`, `List`/`AbstractList`, `Map`/`AbstractMap`. ## The two tie-breakers (hard constraints) When the soft signals conflict, two mechanical constraints decide: - **Must combine multiple roles → interface.** Single inheritance of implementation means a class can extend only one base; if a type must be several things, those must be interfaces. - **Must share mutable per-instance state → abstract class.** Interfaces can't hold instance fields or constructors, so genuinely shared, constructed state forces an abstract base (or composition). ## A worked example Modelling notification senders (email, SMS, push): - They share a **role**: *'can send a Notification'* → an interface `NotificationSender { void send(Notification n); }`. - They also share **state and logic**: a retry policy, a rate-limit counter, a templated send-with-retry algorithm → an abstract `AbstractRetryingSender implements NotificationSender` holding the policy fields and a `final` `send()` that calls an abstract `doSend()` hook. - Result: callers depend on the interface; concrete senders extend the skeleton for reuse, but a future `ThirdPartySender` that already extends a vendor base class can still just implement the interface. ## Default stance When genuinely unsure, **lead with an interface for the contract** and add an abstract class only when shared state or heavy shared implementation justifies spending the single-inheritance slot. This keeps coupling low and preserves the most future flexibility.

  • Your candidate base type would model both 'is a kind of X' AND a capability several unrelated classes share. How do you split it?
    Separate the two concerns: express the cross-cutting capability as an interface so unrelated classes can adopt it, and express the is-a family as an abstract class (which can itself implement that interface). This avoids forcing unrelated classes into the family while still sharing the capability's contract.
  • When would you prefer composition over either an interface-backed abstract class?
    When you want code reuse without the inheritance commitment, or when the relationship is has-a rather than is-a. Hold the collaborator as a field and delegate to it (composition over inheritance). This sidesteps the single-inheritance slot and the fragile-base-class problem, and you can still implement an interface for the public contract.

saying these in an interview costs you the question

  • Choosing an abstract class just to share a couple of methods, ignoring that it forfeits the implementer's single extends slot.
  • Modeling a cross-cutting capability as an abstract class, forcing unrelated types into one hierarchy.
  • Forgetting that needing shared mutable state is a hard signal for an abstract class, not an interface.
  • Believing it must be one or the other — the interface + skeletal abstract class combo is often best.
  • Defaulting to inheritance when composition/delegation would give the reuse without the coupling.

context