0
0
Kotlinprogramming~15 mins

Custom getters and setters in Kotlin - Deep Dive

Choose your learning style9 modes available
Overview - Custom getters and setters
What is it?
Custom getters and setters in Kotlin let you control how a property’s value is read or changed. Instead of just storing a value, you can add code that runs when you get or set the property. This helps you add checks, calculations, or other actions automatically whenever the property is used. It makes your code safer and more flexible without changing how you access the property.
Why it matters
Without custom getters and setters, you would have to write extra functions to check or change values, making your code longer and harder to read. Custom accessors let you keep your code clean and easy to use while still adding important rules or calculations. This helps prevent bugs and makes your program behave exactly how you want whenever properties change.
Where it fits
Before learning custom getters and setters, you should understand basic Kotlin properties and classes. After this, you can learn about property delegation and advanced Kotlin features like data classes and immutability. Custom accessors are a key step to writing professional Kotlin code that is both safe and expressive.
Mental Model
Core Idea
Custom getters and setters are special code blocks that run automatically when you read or write a property, letting you control its behavior behind the scenes.
Think of it like...
It's like having a smart mailbox that checks the mail for you when you open it and only accepts letters that meet certain rules when you put them in.
Property
  │
  ├─ Getter (runs when reading)
  │     └─ Custom code or default return
  └─ Setter (runs when writing)
        └─ Custom code or default store

Access flow:
Read property → Getter code runs → Returns value
Write property → Setter code runs → Updates value or rejects
Build-Up - 7 Steps
1
FoundationBasic Kotlin property syntax
🤔
Concept: Learn how to declare simple properties with default getters and setters.
In Kotlin, a property is declared with a type and optionally initialized. By default, Kotlin creates a getter to return the value and a setter to update it. Example: class Person { var name: String = "" } Here, 'name' has a default getter and setter automatically.
Result
You can read and write 'name' like a normal variable, e.g., person.name = "Anna" and println(person.name).
Understanding default property behavior is essential before customizing how properties work.
2
FoundationWhat are getters and setters?
🤔
Concept: Getters retrieve a property’s value; setters update it. Kotlin hides these by default but lets you customize them.
Every property has a getter and, if mutable, a setter. They are functions behind the scenes: - Getter: called when you read the property. - Setter: called when you assign a new value. Example: val x: Int get() = 42 Here, 'x' always returns 42 when read.
Result
You can control what happens when properties are accessed or changed by customizing these functions.
Knowing that properties are backed by functions helps you see how to add logic to property access.
3
IntermediateWriting a custom getter
🤔Before reading on: do you think a custom getter can change the returned value every time it is called? Commit to your answer.
Concept: You can write a getter that runs code and returns a calculated or modified value instead of just returning a stored field.
Example: class Rectangle(val width: Int, val height: Int) { val area: Int get() = width * height } Here, 'area' has no backing field but calculates the area whenever accessed.
Result
Accessing rectangle.area returns the product of width and height dynamically.
Custom getters let you create properties that behave like functions but look like simple variables.
4
IntermediateWriting a custom setter
🤔Before reading on: do you think a custom setter can reject or modify the value being assigned? Commit to your answer.
Concept: A setter can run code to check or change the value before storing it, or even reject it by not updating the field.
Example: class User { var age: Int = 0 set(value) { if (value >= 0) field = value } } Here, the setter only updates 'age' if the new value is not negative.
Result
Trying to set a negative age will have no effect; the property keeps its old value.
Custom setters help enforce rules and keep data valid automatically.
5
IntermediateBacking fields and 'field' keyword
🤔
Concept: Inside setters and getters, 'field' refers to the actual storage of the property, letting you read or write the stored value.
When you customize accessors, you use 'field' to avoid infinite loops. Example: var name: String = "" set(value) { field = value.trim() } Here, 'field' stores the trimmed value to avoid calling the setter recursively.
Result
The property stores a trimmed version of the string whenever set.
Understanding 'field' is key to safely customizing property behavior without errors.
6
AdvancedCustom accessors without backing fields
🤔Before reading on: can a property have a getter but no backing field? Commit to your answer.
Concept: Properties can have custom getters without storing a value, computing results on the fly instead.
Example: class Circle(val radius: Double) { val circumference: Double get() = 2 * Math.PI * radius } 'circumference' has no backing field; it calculates the value every time.
Result
Accessing circumference always returns the current calculated value based on radius.
This pattern lets you create read-only properties that always reflect current data without extra storage.
7
ExpertPerformance and side effects in accessors
🤔Before reading on: do you think putting heavy computations or side effects in getters/setters is a good practice? Commit to your answer.
Concept: Getters and setters run every time a property is accessed or changed, so putting slow or side-effect code there can cause unexpected behavior or performance issues.
Example of a bad getter: val data: String get() { println("Loading data") Thread.sleep(1000) // slow operation return "value" } This delays every read and prints a message, which can surprise users. Best practice: keep accessors fast and side-effect free.
Result
Slow or side-effectful accessors can cause bugs, slow programs, or confusing behavior.
Knowing the cost of accessors helps you write clean, predictable, and efficient Kotlin code.
Under the Hood
Kotlin compiles properties into Java-style getter and setter methods behind the scenes. When you access a property, the compiler calls the getter method; when you assign a value, it calls the setter method. Custom getters and setters replace or extend these methods with your code. The 'field' keyword accesses the hidden backing field that stores the actual value. Without a backing field, the property is computed dynamically each time.
Why designed this way?
Kotlin’s design aims to combine the simplicity of direct property access with the power of methods. This lets developers write clean code that looks like variable access but can run complex logic. It avoids boilerplate code and keeps APIs simple. The backing field concept prevents infinite recursion and keeps storage separate from logic.
Property Access Flow

┌─────────────┐
│ Property X  │
└─────┬───────┘
      │ read or write
      ▼
┌─────────────┐
│ Getter/Setter│
│ (custom code)│
└─────┬───────┘
      │
      ▼
┌─────────────┐
│ Backing Field│
│ (storage)   │
└─────────────┘

If no backing field, getter computes value dynamically.
Myth Busters - 4 Common Misconceptions
Quick: Does a custom getter always have a backing field? Commit to yes or no.
Common Belief:A custom getter always uses a backing field to store the value.
Tap to reveal reality
Reality:A custom getter can compute and return a value without any backing field at all.
Why it matters:Assuming a backing field exists can lead to confusion and bugs when the property does not store data but calculates it dynamically.
Quick: Can a setter reject a value without throwing an error? Commit to yes or no.
Common Belief:Setters must always update the property value when called.
Tap to reveal reality
Reality:Setters can choose not to update the backing field, effectively rejecting the new value silently.
Why it matters:Not knowing this can cause unexpected behavior where property values don’t change even though assignments happen.
Quick: Is it safe to put heavy computations inside getters? Commit to yes or no.
Common Belief:Getters are just like variables, so putting slow code inside them is fine.
Tap to reveal reality
Reality:Getters run every time the property is accessed, so heavy computations can slow down your program unexpectedly.
Why it matters:Ignoring this can cause performance problems and hard-to-find bugs in real applications.
Quick: Does using 'field' inside a getter or setter call the getter or setter recursively? Commit to yes or no.
Common Belief:Using 'field' inside accessors calls the getter or setter again, causing infinite loops.
Tap to reveal reality
Reality:'field' directly accesses the backing storage and does not call accessors, preventing recursion.
Why it matters:Misunderstanding this can lead to incorrect code or avoiding custom accessors altogether.
Expert Zone
1
Custom setters can be used to trigger side effects like updating UI or notifying observers, but this should be done carefully to avoid unexpected behavior.
2
Backing fields are only available in properties declared with 'var' or 'val' and cannot be used in abstract or interface properties.
3
When multiple custom accessors are used in inheritance, the order and overriding behavior can affect property behavior subtly.
When NOT to use
Avoid custom getters and setters when the logic is complex or involves asynchronous operations; instead, use functions or property delegation. Also, do not use them to perform heavy computations or side effects that should be explicit in method calls.
Production Patterns
In production Kotlin code, custom getters and setters are often used to validate input, lazily compute values, or keep related properties in sync. They are also common in data classes for derived properties and in Android development to update UI state automatically.
Connections
Encapsulation in Object-Oriented Programming
Custom getters and setters implement encapsulation by controlling access to property data.
Understanding custom accessors deepens your grasp of encapsulation, showing how data hiding and validation happen in practice.
Reactive Programming
Custom setters can trigger side effects similar to reactive data streams updating observers.
Knowing this connection helps you design properties that automatically notify changes, bridging imperative and reactive styles.
Database Triggers
Setters acting on property changes are like database triggers that run code when data changes.
This cross-domain link shows how automatic reactions to data changes are a common pattern in software systems.
Common Pitfalls
#1Infinite recursion by calling property inside its own setter.
Wrong approach:var name: String = "" set(value) { name = value.trim() // calls setter again → infinite loop }
Correct approach:var name: String = "" set(value) { field = value.trim() // uses backing field to avoid recursion }
Root cause:Using the property name inside its setter calls the setter recursively instead of accessing storage.
#2Putting slow or side-effect code inside getters causing performance issues.
Wrong approach:val data: String get() { Thread.sleep(1000) // slow operation return "value" }
Correct approach:val data: String get() = cachedValue // fast, no delay
Root cause:Not realizing getters run on every access leads to unexpected delays.
#3Assuming a property always has a backing field and trying to use 'field' in a property without one.
Wrong approach:val area: Int get() = width * height set(value) { field = value } // error: no backing field
Correct approach:val area: Int get() = width * height // read-only property without setter
Root cause:Trying to write to a backing field that does not exist causes compilation errors.
Key Takeaways
Custom getters and setters let you add logic to property access while keeping simple syntax.
The 'field' keyword accesses the actual stored value inside accessors to avoid recursion.
Properties can have getters without backing fields, computing values dynamically on each access.
Avoid heavy computations or side effects in getters and setters to keep code efficient and predictable.
Custom accessors are a powerful tool for encapsulation, validation, and reactive programming patterns in Kotlin.