Phase 3 · Object-Oriented KotlinModule 14~32 min read

Properties, Getters & Backing Fields

Kotlin properties with custom accessors, backing fields, and lateinit.

What you'll learn

In Kotlin you work with properties, not raw fields. That distinction unlocks computed values, validation on assignment, and clean syntax — all while looking like a simple attribute. This module covers custom accessors, the backing field, and lateinit.

By the end you'll be able to:

  • Understand properties vs fields
  • Write computed properties with custom getters
  • Validate assignment with custom setters and the field keyword
  • Defer initialisation with lateinit

Properties, not fields

When you write val name: String, Kotlin creates a property — a field plus an automatic getter (and a setter for var). You access it like an attribute (p.name), but you can customise how it's read or written whenever you need to, without changing any code that uses it.

Computed properties

A property doesn't have to store a value — it can compute one each time it's read, by providing a custom get(). This is perfect for derived values like an area or a full name. There's no method-call syntax; it reads like a plain property:

Circle.kt
class Circle(val radius: Double) {
    // a COMPUTED property — no stored value, calculated on each access
    val area: Double
        get() = 3.14159 * radius * radius

    val circumference get() = 2 * 3.14159 * radius
}

val c = Circle(2.0)
println(c.area)            // 12.56636
println(c.circumference)   // 12.56636

Custom setters & the backing field

A custom set(value) lets you run code when a property is assigned — validation, logging, notifying observers. Inside the accessor, field is the backing field: the actual storage. You must use field (not the property name) to avoid infinite recursion:

Temperature.kt
class Temperature {
    var celsius: Double = 0.0
        set(value) {
            field = value             // 'field' is the backing field
            println("Set to $value°C")
        }

    val fahrenheit get() = celsius * 9 / 5 + 32
}

val t = Temperature()
t.celsius = 25.0
println("${t.fahrenheit}°F")

Use field, not the property name

Inside a setter, writing celsius = value would call the setter again — infinite recursion. Always assign to field, which is the hidden storage the compiler generates for you.

lateinit

Sometimes a non-null property can't be set at construction time — it's wired up later (a common pattern with dependency injection or Android views). lateinit var lets you declare a non-null property and promise to initialise it before first use, avoiding a nullable type:

Service.kt
class Service {
    lateinit var name: String       // "I'll set this before using it"

    fun start(n: String) { name = n }
}

val s = Service()
s.start("Auth")
println(s.name)                     // Auth

Note

Accessing a lateinit property before it's set throws an UninitializedPropertyAccessException. Use it only when you're confident initialisation happens first; otherwise, a nullable type (String?) with the null-safety tools from Module 3 is safer. lateinit also can't be used with primitive types.

Recap & quick check

Key takeaways

  • Kotlin gives you properties (field + accessors), not raw fields — accessed like attributes.
  • A custom get() creates a computed property, recalculated on each access, with no storage.
  • A custom set(value) runs code on assignment; use 'field' (the backing field) to store the value.
  • Assigning to the property name inside its own setter causes infinite recursion — use field.
  • lateinit var declares a non-null property initialised later; it throws if accessed before being set.

Quick check

1. What does a custom get() with no backing field create?

2. Inside a custom setter, what does 'field' refer to?

3. Why must you use 'field' instead of the property name in a setter?

4. What is 'lateinit var' for?

5. What happens if you access a lateinit property before setting it?

Great — your properties can now compute and validate. Next up: Module 15 — Data Classes.