Skip to content

Chapter 5 of 7

Properties and Delegation

Kotlin's property model unifies simple field access with getter and setter logic. A declaration like val name: String creates a property that the compiler backs with a private field and a synthetic accessor method. You can override these accessors when logic is needed, as in val fullName: String get() = "$first $last" for a computed read-only property, or var password: String set(value) { if (value.length >= 8) field = value } for guarded mutation. Inside a custom accessor, the identifier field refers to the backing storage, and a backing field is generated only when you use the default accessors or reference field — otherwise the property is just a function with property syntax.

Initialization strategies vary by use case. lateinit applies only to var properties of non-nullable reference types (not primitives like Int or Boolean) and defers initialization until first use, throwing UninitializedPropertyAccessException if read too early. It shines for dependency injection and Android view binding where you cannot initialize in the constructor, and ::adapter.isInitialized lets you test whether it has been assigned. The lazy { } delegate, by contrast, applies to read-only properties: it computes the value on first access, caches it, and is thread-safe by default (LazyThreadSafetyMode.SYNCHRONIZED) — ideal for expensive or rare computations. Together, they cover the two lazy patterns: lateinit for late-initialized mutable references and lazy for expensive immutable computations.

The by keyword enables two distinct delegation patterns. In class headers, by delegates interface implementation to another object: class Derived(b: Base) : Base by b forwards every method of Base to b without manual forwarding code. For properties, by delegates the get and set logic to a separate object — the receiver just provides getValue() and setValue() operator functions. Built-in delegates cover the most common cases: lazy for deferred initialization, Delegates.observable(initial) { prop, old, new -> ... } for change-notification, and Delegates.vetoable for changes that may be rejected. Custom delegates can implement getValue and setValue themselves, enabling patterns like map-backed properties, validated setters, or even shared remote state.

Top-level properties sit at the file level outside any class and can be val or var — with const val allowed at top level and for primitives or Strings. They make natural global constants and can be restricted to module-internal use with the internal modifier. Kotlin's visibility modifiers extend beyond classes: internal members are visible throughout the same Gradle or IntelliJ module but hidden from other modules, striking a balance between Java's package-private and public. protected limits visibility to the declaring class and its subclasses — unlike Java, it does not extend to the same package. There is no package-private keyword; for package-local visibility, you use internal or a private file-level declaration. The const keyword declares compile-time constants: const val must be a primitive type or String, must be initialized with a literal, and is restricted to the top level or inside an object or companion object. Crucially, const val is inlined at every call site, which is required for use in annotations and for when expression cases that must compile to efficient conditional branches. Plain val can hold any type, may use a custom getter, and can sit anywhere in the class hierarchy, but its value is evaluated at runtime and cannot appear in compile-time contexts.

All chapters
  1. 1Foundations — Types, Variables, and Expressions
  2. 2Null Safety
  3. 3Classes, Objects, and Inheritance
  4. 4Functions, Lambdas, and Scope Functions
  5. 5Properties and Delegation
  6. 6Collections, Sequences, and Strings
  7. 7Coroutines and Flow

Drill it

Reading is not remembering. These come from the Kotlin Programming deck:

Q

What is null safety in Kotlin?

Null safety is a Kotlin feature that eliminates NullPointerException at compile time.By default, variables cannot hold null. You must explicitly declare a type...

Q

How do you declare a nullable type in Kotlin?

Append a ? to the type declaration:var name: String? = nullWithout the ?, the compiler will reject any attempt to assign null.

Q

What is the safe call operator in Kotlin?

The safe call operator ?. allows you to access a property or call a method on a nullable object without risking a NullPointerException:val length = name?.length...

Q

What is the Elvis operator in Kotlin?

The Elvis operator ?: provides a default value when an expression is null:val len = name?.length ?: 0If the left side is null, the right side is used.