skip to content

An overriding member in Kotlin is itself implicitly `open`. What does that mean for deeper subclasses, and how do you stop further overriding with `final override`?

level: seniorimportance: should knowfreq 45%

answer

  1. `override` is implicitly `open`
  2. `final override` seals the member
  3. Default is final; override is the exception
  4. Mid-hierarchy locks an invariant
  5. Applies to properties too

basics

~10 s

When you override something, that override can also be overridden by classes below you — it stays open automatically. Write final override to seal it so no further subclass can change it.

solid answer

~40 s

In Kotlin a member marked `override` is **implicitly `open`** to all further subclasses, even though you did not write `open`. This is a deliberate asymmetry: top-level members are `final` by default, but once a member enters an override chain it stays open so the hierarchy remains extensible. If you want to **seal** an override against deeper subclasses, prefix it with `final` → `final override fun foo()`. The compiler then rejects any attempt to override `foo` lower in the hierarchy. This matters for invariants: a mid-hierarchy class can lock down behavior it relies on. The same applies to overriding properties (`final override val x`). It is the only place `final` is commonly written explicitly in Kotlin, since classes/members are otherwise final by default.

code

kotlin · 7 lines
kotlin
open class Repo { open fun save() {} }
open class CachingRepo : Repo() {
    final override fun save() { /* must run exactly this */ }
}
class AuditRepo : CachingRepo() {
    // override fun save() {}  // ERROR: save is final in CachingRepo
}

go deeper

for a junior

Aware that override exists but may not know it stays open.

for a middle

Knows overrides are implicitly open and that final override seals them.

for a senior

Explains the design rationale and uses final override to protect a mid-hierarchy invariant.

for a principal

Weighs extensibility vs. invariant-protection across an evolving class hierarchy and library API surface.

## The implicit-open rule Kotlin makes classes and members **`final` by default**: you must write `open` to allow overriding. But there is one exception — **an `override` member is implicitly `open`**. Once a member is overridden, that override can itself be overridden further down the chain *without* you writing `open` again. ```kotlin open class A { open fun f() {} } open class B : A() { override fun f() {} } // implicitly open class C : B() { override fun f() {} } // legal — B.f() was still open ``` `B.f()` is overridable in `C` even though `B` only wrote `override`, not `open override`. ## Sealing with `final override` To stop the chain, mark the override `final`: ```kotlin open class B : A() { final override fun f() {} // sealed } class C : B() { // override fun f() {} // ERROR: 'f' in 'B' is final, cannot override } ``` Now `C` (and anything below) cannot override `f`. This is the idiomatic way a middle-of-hierarchy class **protects an invariant** it has established on top of the parent's behavior. ## Why the asymmetry exists - Without it, every override would also need `open` to keep deep hierarchies extensible — noisy and easy to forget. - With `final override` available, you opt *out* explicitly only where you need to lock behavior, which is the rarer case. ## Applies to properties too ```kotlin open class Base { open val id: Int = 0 } open class Mid : Base() { final override val id: Int = 1 } // no deeper override ``` ## Interaction with `super` Sealing an override does not affect calling the parent: a deeper class can still call `super.f()` to reach the *nearest non-final* implementation it inherits, but it cannot supply its own override of a `final` member.

  • Why did Kotlin make overrides implicitly open instead of final like everything else?
    To keep deep hierarchies extensible without repeating `open` on every level; you opt out with `final override` only where locking behavior matters.
  • Does `final override` prevent a subclass from calling `super` on that member?
    No. Subclasses can still call `super.member()`; they just cannot provide their own override of the sealed member.

An override is a door left unlocked for the next room down; final override is bolting that door shut.

saying these in an interview costs you the question

  • Thinking an override must repeat `open` to be overridable further
  • Believing overrides are final by default like other members
  • Not knowing `final override` exists
  • Confusing sealing the member with blocking `super` calls

context