How do I use delegation by `by` keyword to implement interfaces or properties in Kotlin?

In Kotlin, the by keyword is used for delegation. It lets one object delegate behavior to another object instead of implementing everything manually.

There are two common forms:

  1. Interface/class delegation
  2. Property delegation

1. Interface delegation

If a class implements an interface, it can delegate the implementation of that interface to another object using by.

Example

interface Printer {
    fun print(message: String)
}

class ConsolePrinter : Printer {
    override fun print(message: String) {
        println(message)
    }
}

class LoggingPrinter(
    private val printer: Printer
) : Printer by printer

Here, LoggingPrinter implements Printer, but the actual implementation is delegated to printer.

Usage:

fun main() {
    val printer = LoggingPrinter(ConsolePrinter())
    printer.print("Hello Kotlin")
}

Output:

Hello Kotlin

LoggingPrinter does not need to manually implement print() because Printer by printer forwards calls to the given printer.


Overriding delegated behavior

You can still override specific methods if you want custom behavior.

interface Printer {
    fun print(message: String)
}

class ConsolePrinter : Printer {
    override fun print(message: String) {
        println(message)
    }
}

class LoggingPrinter(
    private val printer: Printer
) : Printer by printer {
    override fun print(message: String) {
        println("Logging message: $message")
        printer.print(message)
    }
}

Now calls to print() use the overridden method in LoggingPrinter.

fun main() {
    val printer = LoggingPrinter(ConsolePrinter())
    printer.print("Hello Kotlin")
}

Output:

Logging message: Hello Kotlin
Hello Kotlin

2. Delegating multiple interfaces

A class can delegate multiple interfaces to different objects.

interface Reader {
    fun read(): String
}

interface Writer {
    fun write(value: String)
}

class FileReader : Reader {
    override fun read(): String = "file contents"
}

class ConsoleWriter : Writer {
    override fun write(value: String) {
        println(value)
    }
}

class FileProcessor(
    reader: Reader,
    writer: Writer
) : Reader by reader, Writer by writer

Usage:

fun main() {
    val processor = FileProcessor(FileReader(), ConsoleWriter())

    val text = processor.read()
    processor.write(text)
}

Important note about delegated members

If a delegated object calls one of its own methods internally, it does not dispatch to overrides in the delegating class.

interface Service {
    fun operation()
    fun run()
}

class DefaultService : Service {
    override fun operation() {
        println("Default operation")
    }

    override fun run() {
        operation()
    }
}

class CustomService(
    private val service: Service
) : Service by service {
    override fun operation() {
        println("Custom operation")
    }
}

Usage:

fun main() {
    val service = CustomService(DefaultService())
    service.operation()
    service.run()
}

Output:

Custom operation
Default operation

service.run() is delegated to DefaultService.run(), and inside that object, operation() resolves to DefaultService.operation().


Property delegation

Property delegation lets another object provide the getter and/or setter for a property.

The syntax is:

val propertyName: Type by delegate
var propertyName: Type by delegate

The delegate object must provide:

  • getValue(...) for val
  • getValue(...) and setValue(...) for var

1. Built-in property delegates

Kotlin provides several common delegates.


lazy

lazy initializes a value only when it is first accessed.

val expensiveValue: String by lazy {
    println("Computing value")
    "Hello"
}

fun main() {
    println("Before access")
    println(expensiveValue)
    println(expensiveValue)
}

Output:

Before access
Computing value
Hello
Hello

The initializer runs only once.


observable

Delegates.observable runs code whenever a property changes.

import kotlin.properties.Delegates

var name: String by Delegates.observable("Unknown") { property, oldValue, newValue ->
    println("${property.name} changed from $oldValue to $newValue")
}

fun main() {
    name = "Alice"
    name = "Bob"
}

Output:

name changed from Unknown to Alice
name changed from Alice to Bob

vetoable

Delegates.vetoable can reject a new value.

import kotlin.properties.Delegates

var age: Int by Delegates.vetoable(0) { _, _, newValue ->
    newValue >= 0
}

fun main() {
    age = 25
    println(age)

    age = -5
    println(age)
}

Output:

25
25

The assignment to -5 is rejected.


notNull

Delegates.notNull() is useful for non-null properties initialized later.

import kotlin.properties.Delegates

var username: String by Delegates.notNull()

fun main() {
    username = "alice"
    println(username)
}

If you read username before assigning it, Kotlin throws an exception.


Custom property delegate

You can create your own delegate by implementing getValue and optionally setValue.

import kotlin.reflect.KProperty

class LoggingDelegate {
    private var value: String = ""

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("Reading ${property.name}")
        return value
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
        println("Writing ${property.name}: $newValue")
        value = newValue
    }
}

class User {
    var name: String by LoggingDelegate()
}

Usage:

fun main() {
    val user = User()

    user.name = "Alice"
    println(user.name)
}

Output:

Writing name: Alice
Reading name
Alice

How getValue and setValue work

For:

var name: String by LoggingDelegate()

Kotlin roughly translates property access like this:

val delegate = LoggingDelegate()

delegate.setValue(thisRef, property, "Alice")
val value = delegate.getValue(thisRef, property)

The signatures are:

operator fun getValue(thisRef: Any?, property: KProperty<*>): T
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T)

For top-level properties, thisRef is null.


Delegating to another property

You can also delegate one property to another property reference.

class User {
    var name: String = "Alice"

    var displayName: String by this::name
}

fun main() {
    val user = User()

    println(user.displayName)

    user.displayName = "Bob"

    println(user.name)
}

Output:

Alice
Bob

displayName delegates its storage to name.


Delegating to a map

A common pattern is delegating properties to a Map.

class User(
    private val data: Map<String, Any?>
) {
    val name: String by data
    val age: Int by data
}

Usage:

fun main() {
    val user = User(
        mapOf(
            "name" to "Alice",
            "age" to 30
        )
    )

    println(user.name)
    println(user.age)
}

Output:

Alice
30

For mutable properties, use MutableMap:

class MutableUser(
    private val data: MutableMap<String, Any?>
) {
    var name: String by data
    var age: Int by data
}

Summary

Use case Syntax Meaning
Interface delegation class C(d: I) : I by d Forward interface calls to d
Lazy property val x by lazy { ... } Compute once on first access
Observable property var x by Delegates.observable(...) React to changes
Vetoable property var x by Delegates.vetoable(...) Accept or reject changes
Custom delegate var x by MyDelegate() Delegate getter/setter logic
Map-backed property val x: T by map Read value from map by property name
Property reference var x by this::other Delegate to another property

In short:

class MyClass(delegate: SomeInterface) : SomeInterface by delegate

is for interface delegation, while:

val value by lazy { ... }
var name by MyDelegate()

is for property delegation.

How do I implement interfaces and use multiple inheritance in Kotlin?

In Kotlin, interfaces define a contract that classes can implement. Kotlin supports multiple inheritance of interfaces, but not multiple inheritance of classes.

1. Defining an interface

An interface can declare:

  • abstract properties
  • abstract functions
  • functions with default implementations
interface Drivable {
    val maxSpeed: Int

    fun drive()

    fun stop() {
        println("Stopping the vehicle")
    }
}

Here:

  • maxSpeed must be implemented by any class using the interface.
  • drive() has no body, so it must be implemented.
  • stop() has a default implementation, so overriding it is optional.

2. Implementing an interface

Use : after the class name to implement an interface.

class Car : Drivable {
    override val maxSpeed: Int = 180

    override fun drive() {
        println("The car is driving at up to $maxSpeed km/h")
    }
}

Usage:

fun main() {
    val car = Car()

    car.drive()
    car.stop()
}

Output:

The car is driving at up to 180 km/h
Stopping the vehicle

3. Implementing multiple interfaces

A class can implement more than one interface by separating them with commas.

interface Flyable {
    fun fly() {
        println("Flying")
    }
}

interface Swimmable {
    fun swim() {
        println("Swimming")
    }
}

class Duck : Flyable, Swimmable

Usage:

fun main() {
    val duck = Duck()

    duck.fly()
    duck.swim()
}

4. Handling conflicting default implementations

If two interfaces provide a function with the same signature, the implementing class must override it.

interface Printer {
    fun print() {
        println("Printing from Printer")
    }
}

interface Scanner {
    fun print() {
        println("Printing from Scanner")
    }
}

class AllInOneMachine : Printer, Scanner {
    override fun print() {
        super<Printer>.print()
        super<Scanner>.print()
        println("Printing from AllInOneMachine")
    }
}

Usage:

fun main() {
    val machine = AllInOneMachine()

    machine.print()
}

Output:

Printing from Printer
Printing from Scanner
Printing from AllInOneMachine

The syntax:

super<Printer>.print()

means “call the print() implementation from the Printer interface.”

5. Interfaces with properties

Interfaces can declare properties, but they do not store state directly like classes do.

interface Identifiable {
    val id: String
}

class User(
    override val id: String,
    val name: String
) : Identifiable

Usage:

fun main() {
    val user = User("u123", "Alice")

    println(user.id)
    println(user.name)
}

6. Kotlin and multiple inheritance

Kotlin allows:

class Duck : Flyable, Swimmable

But Kotlin does not allow multiple class inheritance:

open class Animal
open class Machine

// Not allowed in Kotlin
class RobotDog : Animal(), Machine()

Instead, Kotlin allows:

  • one superclass
  • multiple interfaces
open class Animal {
    fun eat() {
        println("Eating")
    }
}

interface Runnable {
    fun run()
}

interface Trainable {
    fun train()
}

class Dog : Animal(), Runnable, Trainable {
    override fun run() {
        println("Dog is running")
    }

    override fun train() {
        println("Dog is training")
    }
}

Key syntax

class ClassName : InterfaceName
class ClassName : InterfaceOne, InterfaceTwo
class ClassName : SuperClass(), InterfaceOne, InterfaceTwo

Summary

  • Use interface to define behavior.
  • Use : to implement interfaces.
  • Use override to implement interface members.
  • Kotlin supports multiple interface inheritance.
  • Kotlin does not support inheriting from multiple classes.
  • If interfaces have conflicting default methods, override the method and choose which parent implementation to call using super<InterfaceName>.