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`?
answer
- `override` is implicitly `open`
- `final override` seals the member
- Default is final; override is the exception
- Mid-hierarchy locks an invariant
- Applies to properties too
basics
~10 sWhen 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 sIn 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 linesopen 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
Aware that override exists but may not know it stays open.
Knows overrides are implicitly open and that final override seals them.
Explains the design rationale and uses final override to protect a mid-hierarchy invariant.
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