Delegation Workbook

Nine short exercises on Kotlin delegation — class delegation with by, lazy and observable properties, map-backed properties, writing a custom delegate, and lateinit versus lazy.

Practice problems for The by Keyword: Delegation Without the Boilerplate. Each takes a minute or two. Every exercise comes with a runnable editor — fill in the TODO, press Run to compile and run it on JetBrains’ Kotlin compiler server, then click Show answer to check yourself. Nothing here is a trick question, just direct practice of the syntax from the lesson.

class delegation

1. Delegate an interface

Write class LoggingList<T>(private val inner: MutableList<T>) : MutableList<T> that forwards everything to inner but prints before each add.

Try it — edit, then press Run class LoggingList<T>( private val inner: MutableList<T> = mutableListOf() ) : MutableList<T> by inner { // TODO: override add(element) to print before delegating to inner.add } fun main() { val list = LoggingList<String>() list.add("hello") println(list) }
Show answer Hide answer
class LoggingList<T>(
    private val inner: MutableList<T> = mutableListOf()
) : MutableList<T> by inner {
    override fun add(element: T): Boolean {
        println("adding $element")
        return inner.add(element)
    }
}

by inner generates every forwarding method; you override only what you want to change.

2. Delegate, then add methods

Write a Stack<T> that delegates MutableList<T> behavior to an inner list and adds push(x) and pop().

Try it — edit, then press Run class Stack<T>( private val items: MutableList<T> = mutableListOf() ) : MutableList<T> by items { // TODO: add push(x) and pop() backed by items } fun main() { val stack = Stack<Int>() stack.add(1) println(stack) }
Show answer Hide answer
class Stack<T>(
    private val items: MutableList<T> = mutableListOf()
) : MutableList<T> by items {
    fun push(x: T) = items.add(x)
    fun pop(): T = items.removeLast()
}

by items forwards the whole MutableList surface; you write only what’s new — and it works even though items is composed, not inherited.

delegated properties

3. Compute once, lazily

Declare a val config that loads from disk on first access and caches the result.

Try it — edit, then press Run data class Config(val name: String) fun loadConfigFromDisk(): Config { println("loading from disk...") return Config("prod") } // TODO: declare 'config' so loadConfigFromDisk() runs only on first access (by lazy) val config: Config = loadConfigFromDisk() fun main() { println(config) println(config) }
Show answer Hide answer
val config: Config by lazy {
    loadConfigFromDisk()
}

The block runs at most once, on first read.

4. React to changes

Declare a var name that prints "<old> -> <new>" after each assignment.

Try it — edit, then press Run import kotlin.properties.Delegates // TODO: make 'name' print the old and new values after each assignment (Delegates.observable) var name: String = "" fun main() { name = "Ada" name = "Grace" println(name) }
Show answer Hide answer
import kotlin.properties.Delegates

var name: String by Delegates.observable("") { _, old, new ->
    println("$old -> $new")
}

5. Back a property with a map

Declare class Config(source: Map<String, Any?>) with a val host: String and val port: Int read from the map by their own names.

Try it — edit, then press Run class Config(private val source: Map<String, Any?>) { // TODO: read host: String and port: Int from source by their own names (by source) } fun main() { val config = Config(mapOf("host" to "localhost", "port" to 8080)) // TODO: after adding host and port, print them println("config created") }
Show answer Hide answer
class Config(private val source: Map<String, Any?>) {
    val host: String by source
    val port: Int by source
}

custom delegates

6. Write a delegate

Write a delegate UpperCase so that var name: String by UpperCase() stores every assignment uppercased.

Try it — edit, then press Run import kotlin.reflect.KProperty class UpperCase { private var stored = "" operator fun getValue(thisRef: Any?, property: KProperty<*>) = stored operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { // TODO: store the value uppercased stored = value } } var name: String by UpperCase() fun main() { name = "hello" println(name) }
Show answer Hide answer
import kotlin.reflect.KProperty

class UpperCase {
    private var stored = ""
    operator fun getValue(thisRef: Any?, property: KProperty<*>) = stored
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        stored = value.uppercase()
    }
}

7. A read-only delegate

Write the minimal delegate Constant42 so that val answer: Int by Constant42() always reads as 42.

Try it — edit, then press Run import kotlin.reflect.KProperty class Constant42 { // TODO: add a getValue that always returns 42 operator fun getValue(thisRef: Any?, property: KProperty<*>) = 0 } val answer: Int by Constant42() fun main() { println(answer) }
Show answer Hide answer
import kotlin.reflect.KProperty

class Constant42 {
    operator fun getValue(thisRef: Any?, property: KProperty<*>) = 42
}

val answer: Int by Constant42()

A read-only (val) delegate needs only getValue; a var would also need setValue.

lateinit vs lazy

8. Assigned from outside

You have a non-null var set by a framework after construction, not computed by the object itself. Which tool — and declare it for a repository: UserRepository.

Try it — edit, then press Run class UserRepository // TODO: declare 'repository' as a non-null var a framework assigns later (lateinit) var repository: UserRepository = UserRepository() fun main() { // pretend a framework assigns it after construction repository = UserRepository() println(repository) }
Show answer Hide answer
lateinit var repository: UserRepository

lateinit — for a non-null var something else assigns later. lazy is for a val that computes itself.

9. Reject bad assignments

Declare a var age that refuses negative values (keeping the previous value) using Delegates.vetoable.

Try it — edit, then press Run import kotlin.properties.Delegates // TODO: make 'age' reject negative assignments, keeping the previous value (Delegates.vetoable) var age: Int = 0 fun main() { age = 30 age = -5 // should be rejected once solved println(age) }
Show answer Hide answer
import kotlin.properties.Delegates

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

vetoable runs before the assignment and rejects it when the handler returns false.


Going deeper: lazy and a custom delegate

12. Compute once, cache forever

Give expensive a value that runs its initializer only on first read. The test checks the block runs exactly once across two reads.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert var calls = 0 class Box { //sampleStart val expensive: Int = run { calls++; 99 } // TODO: make this lazy so calls stays 1 //sampleEnd } class Test { @Test fun deferredAndCachedOnce() { calls = 0 val b = Box() Assert.assertEquals("not run until first read", 0, calls) Assert.assertEquals(99, b.expensive) Assert.assertEquals(99, b.expensive) Assert.assertEquals("run exactly once", 1, calls) } }
Show answer Hide answer
val expensive: Int by lazy { calls++; 99 }

by lazy { } runs the block on first access and caches the result; later reads return the cached value without re-running it. (It’s thread-safe by default; pass LazyThreadSafetyMode.NONE when the property is single-threaded.)

13. A transforming delegate

Write a delegate so that assigning to name stores it uppercased. Implement getValue/setValue.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert import kotlin.reflect.KProperty class Test { @Test fun upper() { val u = User() u.name = "ada" Assert.assertEquals("ADA", u.name) } } //sampleStart class UpperCase { private var stored = "" // TODO: getValue returns stored; setValue stores value.uppercase() operator fun getValue(thisRef: Any?, property: KProperty<*>): String = stored operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { stored = value } } class User { var name: String by UpperCase() } //sampleEnd
Show answer Hide answer
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    stored = value.uppercase()
}

by wires the property’s reads and writes to the delegate’s getValue/setValue. The property argument is a KProperty carrying metadata (its name), so one delegate can serve many properties.


Back to the lesson, The by Keyword, or on to the next one: generics.

Comments