skip to content

Now that Kotlin interfaces can have default method bodies and property accessors, when would you still choose an abstract class over an interface?

level: seniorimportance: should knowfreq 45%

answer

  1. Interface = no state, no constructor, no `init`
  2. Abstract class = backing fields + constructor + `init`
  3. Many interfaces vs one superclass
  4. Abstract class can mark members `final`/`protected`
  5. No state? Prefer interface for composability

basics

~20 s

Use an interface when you only need behavior contracts and want a type to implement several. Use an abstract class when you need stored state, a constructor, or initialization logic, since interfaces can't hold any of those.

solid answer

~50 s

Both can carry concrete behavior now, so the deciding factor is **state and identity**. Abstract classes can have backing fields (stored state), constructors with parameters and `init` blocks, and ordered initialization—interfaces have none of that. A class can implement many interfaces but extend only one class, so interfaces win when you need multiple-supertype composition. Interfaces let you declare property *contracts* and computed accessors but cannot store a value; abstract classes can declare `protected` stored state and template-method scaffolding around it. Choose an abstract class when implementers must share real state or a constrained construction path; choose an interface for pure capability/behavior contracts, mixins, and when a single class must satisfy several of them. Visibility also differs: interface members are public/open and can't be `final` or hold non-public state, while abstract classes support `protected`, `final` overrides, and private helpers with fields.

code

kotlin · 19 lines
kotlin
// State + init + final template method => abstract class
abstract class Game {
    private var started = false
    fun play() {            // final by default in classes
        check(!started); started = true
        setup(); loop()
    }
    protected abstract fun setup()
    protected abstract fun loop()
}

// Pure capabilities, composable => interfaces
interface Pausable { fun pause() }
interface Savable  { fun save() = println("saved") }
class Chess : Game(), Pausable, Savable {
    override fun setup() {}
    override fun loop() {}
    override fun pause() {}
}

go deeper

for a junior

Knows interfaces are for contracts and classes can hold data, but may not articulate the precise state/constructor distinction.

for a middle

Lists that interfaces lack state/constructor and that you can implement many interfaces but extend one class.

for a senior

Gives a clear decision framework around state, init, final members, visibility, and composition, with examples.

for a principal

Weighs API evolution, binary compatibility, and team conventions when choosing interface vs abstract class for a long-lived public API.

## They now overlap on behavior Since Kotlin interfaces support **default method bodies** and **property accessors (`get()`)**, the old "interface = no code" rule is gone. So the choice hinges on what *only one of them* can do. ## What only an abstract class can do - **Stored state / backing fields.** An abstract class can hold real fields: `protected var state = 0`. Interfaces have no backing field, so they cannot store anything. - **Constructors and `init`.** Abstract classes have constructors (with parameters), `init` blocks, and a defined initialization order. Interfaces have no constructor, so they cannot run construction-time logic or take parameters. - **Non-public members with state.** Abstract classes support `protected`/`private` members and private fields for internal scaffolding. Interface members are `public` and `open`; you can't make them `final` or back them with hidden state. - **`final` members.** An abstract class can mark a method `final` to forbid overriding (template-method pattern). Interface members are always open. ## What only an interface can do - **Multiple-supertype composition.** A class can implement many interfaces but extend only one class. Interfaces are the tool for mixins/capability composition. - **Lightweight contracts** with optional computed defaults, no constructor obligations. ## Decision guide ```kotlin // Need shared mutable/initialized state + single hierarchy -> abstract class abstract class Repository(private val conn: Connection) { // ctor + state init { conn.open() } // init logic protected val cache = mutableMapOf<String, Any>() // backing field abstract fun load(id: String): Any fun cached(id: String) = cache.getOrPut(id) { load(id) } // could be final } // Need composable capability contracts -> interface(s) interface Cacheable { val key: String } interface Timestamped { val createdAt: Long; fun age(now: Long) = now - createdAt } class Doc(override val key: String, override val createdAt: Long) : Cacheable, Timestamped ``` ## Rule of thumb - Pick an **abstract class** when implementers must **share real state, a constructor, or initialization**, or you want to lock down some methods as `final`. - Pick an **interface** when you're defining **pure behavior/capability contracts**, especially if a type should satisfy **several** of them. - When in doubt and there's no state, prefer the interface — it's more composable and avoids burning the single allowed superclass slot.

  • If neither state nor a constructor is needed, which is the default choice and why?
    Prefer an interface: it is more composable (a type can implement several), it doesn't consume the single allowed superclass slot, and default methods still let you ship behavior.
  • Can you forbid overriding a method in an interface?
    No. Interface members are always open; to make a method `final` you need an (abstract) class.

An interface is a job description (capabilities required); an abstract class is a partially-built machine with internal parts already wired in that you finish assembling.

saying these in an interview costs you the question

  • Saying interfaces can now hold state, so abstract classes are obsolete
  • Forgetting that interfaces have no constructor/init
  • Not mentioning multiple-supertype composition as the interface advantage
  • Claiming you can mark interface members `final`

context