Question

What the `by` Keyword Does in Kotlin: Delegated Properties Explained

kotlindelegateskeyword

Question

While developing for Android, I sometimes see Kotlin code like this:

var someModel: SomeViewModel by notNullAndObservable { vm ->
    // ...
}

I do not understand what the by keyword means here or what role it plays in this property declaration. How does by work in Kotlin, and what is it doing in examples like this?

Short Answer

By the end of this page, you will understand how Kotlin uses the by keyword for property delegation, why it appears in Android and Kotlin codebases, and how it changes the way a property stores or computes its value. You will also see how delegated properties differ from normal properties, how they work step by step, and when developers use them in real projects.

Concept

In Kotlin, the by keyword often means delegation.

In your example:

var someModel: SomeViewModel by notNullAndObservable { vm ->
    // ...
}

someModel is not managing its value entirely by itself. Instead, Kotlin delegates the work of getting and setting the property to another object returned by notNullAndObservable(...).

What is a delegated property?

A delegated property is a property whose behavior is handled by a delegate object.

Instead of writing your own custom getter and setter like this:

var someModel: SomeViewModel = ...
    get() = field
    set(value) {
        field = value
        // extra logic
    }

You can write:

var someModel: SomeViewModel by someDelegate

Kotlin then translates property access behind the scenes so that:

  • reading someModel calls the delegate's getValue(...)
  • writing someModel = ... calls the delegate's setValue(...)

Why this matters

Delegated properties are useful when a property needs extra behavior, such as:

  • lazy loading
  • validation
  • observing changes
  • storing values in maps
  • enforcing non-null initialization later
  • connecting a property to shared preferences, state holders, or framework APIs

In Android development, this is common because many values are not available immediately at object creation time, or because developers want code to react automatically when a property changes.

What by means in your example

The name notNullAndObservable strongly suggests a custom delegate that combines two behaviors:

  • the property must not be read before it is initialized
  • when the property changes, a callback runs

So this line likely means:

  • someModel starts without a usable value
  • if you access it too early, the delegate may throw an error
  • once you assign a value, the delegate stores it
  • when the value changes, the lambda { vm -> ... } is called

The exact behavior depends on how notNullAndObservable is implemented, but the role of by is to tell Kotlin: use that delegate object to control this property.

Mental Model

Think of a normal property as a box that stores its own value.

A delegated property is more like a front desk clerk.

When you try to read or write the property, you are not opening the box directly. You are asking the clerk to handle the request.

  • property = value → the clerk decides how to store it
  • println(property) → the clerk decides how to return it
  • extra rules can happen in between, like logging, validation, or refusing access before setup

So by is Kotlin's way of saying:

"This property is handled by someone else."

That "someone else" is the delegate object.

Take Quiz

Syntax and Examples

Basic delegated property syntax

var name: String by delegateObject

For a read-only property:

val name: String by delegateObject

The delegate object must provide operator functions such as:

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

val only needs getValue. var needs both getValue and setValue.

Example 1: Using Kotlin's built-in lazy

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

fun main() {
    println(message)
    println(message)
}

What happens?

  • message is not computed immediately
  • the first time you read it, the lambda runs
  • after that, Kotlin remembers the result

Output:

Computing message
Hello
Hello

Example 2: A custom observable delegate

Kotlin provides a built-in delegate for observing property changes:

import kotlin.properties.Delegates

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

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

Output:

username:  -> alice
username: alice -> bob

Example 3: Similar idea to your Android example

import kotlin.properties.Delegates

var model: String by Delegates.observable("initial") { _, oldValue, newValue ->
    println("Model changed from $oldValue to $newValue")
}

This means the property is still used like a normal variable, but the delegate adds extra behavior each time it changes.

Step by Step Execution

Consider this example:

import kotlin.properties.Delegates

var status: String by Delegates.observable("offline") { _, oldValue, newValue ->
    println("Status changed from $oldValue to $newValue")
}

fun main() {
    println(status)
    status = "online"
    println(status)
}

Step-by-step

1. Property declaration

var status: String by Delegates.observable("offline") { ... }

Kotlin creates a delegate object using Delegates.observable(...).

That delegate stores the current value, starting with "offline".

2. First read

println(status)

This does not read a normal backing field directly.

Instead, Kotlin calls the delegate's getValue(...) function.

The delegate returns "offline".

Output:

offline

3. Write a new value

status = "online"

Kotlin calls the delegate's setValue(...) function.

The delegate:

  • sees the old value: "offline"
  • stores the new value: "online"
  • runs the callback

Output:

Status changed from offline to online

4. Second read

println(status)

Again, Kotlin calls getValue(...) on the delegate.

Now the stored value is "online", so that is what gets printed.

Output:

online

Mental trace

You can think of Kotlin rewriting the property access roughly like this:

val delegate = Delegates.observable("offline") { _, oldValue, newValue ->
    println("Status changed from $oldValue to $newValue")
}

println(delegate.getValue(...))
delegate.setValue(..., "online")
println(delegate.getValue(...))

The actual generated code is more detailed, but this is the core idea.

Real World Use Cases

Delegated properties are used in many practical Kotlin situations.

1. Lazy initialization

val config by lazy { loadConfigFromDisk() }

Use this when an object is expensive to create and should only be built if needed.

2. Observing state changes

var score by Delegates.observable(0) { _, old, new ->
    println("Score changed: $old -> $new")
}

Useful for UI state, settings, or debugging value changes.

3. Preventing use before initialization

import kotlin.properties.Delegates

var service: String by Delegates.notNull()

Useful when a property cannot be assigned in the constructor but must be set before use.

4. Validation before accepting a value

import kotlin.properties.Delegates

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

The delegate can reject invalid updates.

5. Android and UI frameworks

Delegation is common in Android and modern Kotlin UI code for:

  • view binding helpers
  • fragment arguments
  • state properties
  • preference-backed values
  • lifecycle-aware accessors

6. Map-backed properties

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

This is useful when reading JSON-like data, configuration, or dynamic records.

Take Quiz

Real Codebase Usage

In real projects, developers use delegated properties to reduce boilerplate and centralize repeated logic.

Common patterns

Guarding access until initialization

A service or view model may not be available immediately:

import kotlin.properties.Delegates

class Controller {
    var repository: String by Delegates.notNull()
}

This avoids nullable types when the value should definitely exist before real use.

Reacting to changes automatically

import kotlin.properties.Delegates

class Settings {
    var theme: String by Delegates.observable("light") { _, old, new ->
        if (old != new) println("Theme updated")
    }
}

This is common for syncing state, updating UI, logging, or triggering side effects.

Encapsulating repeated property rules

Teams often create custom delegates to avoid repeating getter/setter logic.

For example, a custom delegate might:

  • trim strings automatically
  • save values to preferences
  • validate input
  • log every change
  • connect properties to a framework object

Cleaner APIs

Instead of this:

private var _token: String? = null

var token: String
    get() = _token ?: error("Token not initialized")
    set(value) {
        _token = value
        println("Token updated")
    }

A delegate can package that behavior into reusable logic.

Why teams like this approach

  • less repeated code
  • more readable property declarations
  • reusable behavior across many classes
  • fewer manual getter/setter mistakes
  • clear intent at the declaration site

Common Mistakes

1. Thinking by always means inheritance or copying

It does not copy a value and it does not mean subclassing.

In property declarations, by means the property is delegated.

2. Assuming the property has a normal backing field

A delegated property may not use a regular backing field in the way a normal property does. The delegate controls storage and behavior.

3. Forgetting that var needs both get and set support

Broken idea:

class ReadOnlyDelegate {
    operator fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): String {
        return "hello"
    }
}

var name: String by ReadOnlyDelegate()

This fails because var requires setValue(...) too.

Fix:

  • use val, or
  • implement setValue(...)

4. Reading Delegates.notNull() before assigning

Broken code:

import kotlin.properties.Delegates

var token: String by Delegates.notNull()

fun main() {
    println(token)
}

This throws an exception because the property was never initialized.

Fix:

import kotlin.properties.Delegates

var token: String by Delegates.notNull()

fun main() {
    token = "abc123"
    println(token)
}

5. Putting too many side effects into delegates

A delegate should make property behavior clearer, not surprising.

Avoid delegates that silently:

  • make network calls on simple reads
  • write to disk unexpectedly
  • trigger large chains of hidden logic

6. Confusing delegated properties with by used elsewhere

Kotlin also uses by in interface delegation, for example:

class MyList<T>(private val inner: MutableList<T>) : MutableList<T> by inner

That is related to delegation in general, but it is not the same as a delegated property.

Comparisons

Delegated property vs normal property

FeatureNormal propertyDelegated property
StorageUsually stored directly in the classOften stored/managed by another object
Custom behaviorManual getter/setterReusable delegate logic
BoilerplateCan grow quicklyOften shorter
Read/write interceptionYou write it yourselfDelegate handles it

by vs = in property declarations

SyntaxMeaning
var x = 10Store the value directly in the property
var x by delegateLet another object control reads/writes

Common built-in delegates

DelegatePurpose
lazyCompute value only when first needed
Delegates.observableRun code after value changes
Delegates.vetoableAccept or reject new values
Delegates.notNullRequire assignment before first read

Delegated properties vs custom getters/setters

ApproachBest when
Custom getter/setterLogic is specific to one property
DelegateLogic should be reusable across many properties

Property delegation vs interface delegation

Both use by, but they apply to different things.

KindExamplePurpose
Property delegationvar x by delegateDelegate property access
Interface delegationclass A(b: B) : B by bForward interface implementation to another object
Take Quiz

Cheat Sheet

Quick syntax

val name: Type by delegate
var name: Type by delegate

What by means

  • Delegate property behavior to another object
  • Reads call getValue(...)
  • Writes call setValue(...) for var

Delegate requirements

For val

operator fun getValue(thisRef: Any?, property: KProperty<*>): Type

For var

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

Built-in delegates

val config by lazy { loadConfig() }
var name by Delegates.observable("") { _, old, new ->
    println("$old -> $new")
}
var token: String by Delegates.notNull()
var age by Delegates.vetoable(0) { _, _, new -> new >= 0 }

Key rule

  • = assigns a value directly
  • by delegates the property to another object

Common edge cases

  • Delegates.notNull() throws if read before assignment
  • var delegates must support setValue(...)
  • lazy is for val, not mutable var
  • by can also be used for interface delegation, which is a different feature

FAQ

What does the by keyword mean in Kotlin?

In many contexts, by means delegation. For properties, it means another object handles getting and setting the property value.

Is by the same as assigning a value?

No. = stores a value directly. by connects the property to a delegate object that controls access.

Why is by common in Android Kotlin code?

Android code often needs lazy setup, state observation, and delayed initialization. Delegated properties make those patterns shorter and cleaner.

What is a delegated property in Kotlin?

A delegated property is a property whose behavior is implemented by another object through getValue and optionally setValue.

What happens when I use Delegates.notNull()?

The property must be assigned before you read it. If you read it too early, Kotlin throws an exception.

Can I create my own delegate?

Yes. You can write a class with operator fun getValue(...) and operator fun setValue(...) and use it with by.

Is by only for properties?

No. Kotlin also uses by for interface delegation, but that is a separate use case from delegated properties.

When should I use a delegate instead of a custom setter?

Use a delegate when the behavior is reusable or when you want cleaner property declarations. Use a custom setter when the logic is small and specific to one property.

Take Quiz

Mini Project

Description

Build a small Kotlin class that tracks an app setting using delegated properties. This project demonstrates how by can add behavior to a property without writing manual getters and setters every time. You will use an observable delegate so changes are logged automatically.

Goal

Create a settings class where property changes are observed and printed whenever the value is updated.

Requirements

  1. Create a Kotlin class named AppSettings.
  2. Add a mutable property named theme using by Delegates.observable.
  3. Start the property with the value "light".
  4. Print the old and new values whenever theme changes.
  5. In main, create an AppSettings object and change the theme at least twice.
Take Quiz

Keep learning

Related questions

View more