skip to content

Delegation vs Inheritance

Delegation lets you reuse an implementation without inheriting its fragility, and you can override individual members while forwarding the rest. Interviewers ask for a concrete case where extending a class would have broken and by would not.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

In Kotlin, what does it mean to implement an interface 'by' delegating to another object, and why might you prefer that over extending an open base class?

level: juniorimportance: must knowfreq 65%

answer

  1. by = auto-generated forwarding methods
  2. composition over inheritance
  3. exposes interface, hides delegate internals
  4. avoids fragile base class
  5. delegate captured at construction

basics

~20 s

The 'by' keyword lets a class implement an interface by handing every method call to another object you give it. You reuse behavior by wrapping an object instead of subclassing it, so you don't depend on a parent class's internals.

solid answer

~40 s

Kotlin class delegation, written `class Wrapper(d: Iface) : Iface by d`, makes the compiler generate forwarding methods for every member of `Iface`, each calling the stored delegate `d`. This is composition: you hold a reference to a collaborator and reuse its behavior, rather than inheriting it. Preferring it over `open` inheritance avoids the fragile base class problem, where overriding methods couple you to the parent's private call sequences. Delegation only exposes the interface contract, not the delegate's implementation, so internal changes can't break you. You can still selectively override any member in the wrapper, and the compiler keeps forwarding the rest. The delegate is captured at construction; only members declared in the delegated interface are forwarded.

code

kotlin · 7 lines
kotlin
interface Printer { fun print(msg: String) }
class ConsolePrinter : Printer { override fun print(msg: String) = println(msg) }

// Delegation: no boilerplate, reuse without inheritance
class PrefixPrinter(p: Printer) : Printer by p

fun main() { PrefixPrinter(ConsolePrinter()).print("hi") }

go deeper

for a junior

Knows by forwards interface calls to a held object and that it's composition not inheritance.

for a middle

Can show selective override plus auto-forward, and explain it exposes only the interface.

for a senior

Frames it as the standard answer to the fragile base class problem and discusses captured-at-construction semantics.

for a principal

Weighs API-surface implications: delegation keeps coupling to a stable interface contract, aiding evolvability and testability.

## What class delegation is Kotlin has first-class support for the *delegation pattern* (a.k.a. composition) through the `by` keyword. Instead of subclassing, you declare that your class implements an interface and forwards the work to a held object — the *delegate*. ```kotlin interface Repository { fun load(id: Int): String; fun save(v: String) } class LoggingRepository(private val inner: Repository) : Repository by inner { override fun save(v: String) { println("saving $v") inner.save(v) // selectively override, still call through } // load() is auto-forwarded to inner by the compiler } ``` For every member of `Repository` that you do **not** override, the compiler synthesizes a method whose body is `return inner.member(...)`. You write zero boilerplate. ## Inheritance vs delegation - **Inheritance** (`open class Base` + `class Sub : Base()`): `Sub` gains all of `Base`'s implementation and can `override open` members. It creates an *is-a* relationship and a tight coupling to the base. - **Delegation** (`class W(d: I) : I by d`): `W` *has-a* delegate and exposes only the interface `I`. It is *composition over inheritance*. ## The fragile base class problem With inheritance, a subclass can break when the base class changes its **internal** behavior — for example if the base's `addAll` starts calling `add` internally, an override of `add` may run more times than expected. Because delegation forwards only the public interface and never participates in the base's private call sequence, it sidesteps this fragility. ## Key facts - Only the members of the **delegated interface** are forwarded; you can delegate to many interfaces at once. - The delegate expression is evaluated once and **captured at construction**; it does not auto-update. - You delegate to **interfaces**, not to open classes — `by` requires an interface (or a delegate type that supplies the interface). ## Recall Think of `by` as auto-generated forwarding glue that gives you reuse without an `is-a` chain.

  • Can you delegate to a concrete open class with `by`?
    No. `by` delegates an interface to a value of that interface type. You delegate behavior described by an interface, not by extending a class.
  • Does `by` generate any runtime reflection?
    No. The compiler statically generates plain forwarding methods at compile time; there's no reflection cost.

A receptionist (wrapper) forwards calls to the right department (delegate) without doing the department's job or knowing its internals.

saying these in an interview costs you the question

  • Saying `by` means subclassing or `extends`
  • Claiming delegation copies the delegate's fields/state into the wrapper
  • Thinking you must hand-write each forwarding method
  • Believing you can delegate a concrete class instead of an interface

context

open as a page

When you override a member in a class that uses `by` delegation, do the OTHER auto-forwarded members start calling your override or the delegate's own implementation? Explain the trap.

level: middleimportance: must knowfreq 55%

basics

~20 s

Other forwarded methods still call the delegate's own versions, not your override. The delegate doesn't know about your wrapper, so any internal calls it makes go to itself. Your override only affects calls made directly on the wrapper.

open as a page

What is the fragile base class problem, and how does Kotlin's class delegation help you avoid it compared to using `open` inheritance?

level: middleimportance: should knowfreq 45%

basics

~20 s

The fragile base class problem is when a subclass quietly breaks because the parent class changed how it works inside, even if the parent's public methods look the same. Delegation avoids it by only using the parent's public interface, not its internals.

open as a page

When you write `class W(val d: I) : I by d`, when is the delegate expression evaluated, and what surprising behavior arises if you pass a property versus a fresh expression? Contrast with overriding the property.

level: seniorimportance: should knowfreq 30%

basics

~20 s

The delegate is evaluated once when the object is built, and that exact value is stored and used for all forwarding. If you later change a property, the forwarders keep using the original delegate they captured, which can surprise you.

open as a page

How can you compose behavior from multiple interfaces using `by`, and what are the design and equals/hashCode/identity caveats versus a single inheritance chain?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

A class can delegate several interfaces to several different objects at once, mixing in behaviors. Unlike single inheritance you can combine many sources. But the wrapper is a new object, so identity and equals/hashCode are its own unless you forward them too.

open as a page