Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now


In the previous lesson, you were briefly introduced to properties as a member of Kotlin classes. They’re often used to describe the object’s attributes or hold state. Look again at the following example:

class Food(val name: String, val price: String)

Constructor Properties

Although you’ve done this many times already, now is a good time to dig slightly deeper into the definition of a class.

class Food(var name: String, var price: String)
kvoza vibi Qeeb

class Food(var name: String, var price: String)

fun main() {
  // with named arguments
  val oneTomato = Food(
    name = "Tomato",
    price = "2.0"
// without named arguments
val twoTomato = Food("3.0", "Tomato")
println( // "3.0"
println(twoTomato.price) // "Tomato"

Mutability and Immutability

Take a look at the statement var name: String. This is how you define variables in functions and define class level variables or properties. = "Fresh Tomato"
  println( // "Fresh Tomato"
class RealFood(val name: String, var price: String)
fun main() {
  // create RealFood
  val realTomato = RealFood("Tomato", "2.5")
  // Error: Val cannot be reassigned = "Real Tomato"

Access Modifiers

You’ve seen how to define properties in a class and how to control their mutability. Now, let’s talk about access modifiers. These are keywords that control the visibility of properties and methods in a class.

class SecretFood {
  private val name
    get() = "Secret Tomato"
  val price = "3.0"

Default Values

If you can reasonably assume what the value of a property should be when the type is initialized, you can give that property a default value. It doesn’t make sense to create a default name or price for a food, but imagine there’s a new property type to indicate what kind of food it is. Create a food class that has a default value:

class BetterFood(
  val name: String,
  var price: String,
  var kind: String = "Vegetable"
fun main() {
  // create BetterFood
  val betterFood = BetterFood("Tomato", "4.0")
  // reassign kind
  betterFood.kind = "Fruit"
  // create BetterFood which is Fruit
  val betterFood2 = BetterFood("Tomato", "4.0", "Fruit")

Custom Accessors

Many properties work fine with the default accessor implementation, in which dot notation returns the value directly, and an assignment statement sets the value. Properties can also be defined with custom getter and setter methods. If a custom setter is provided, the property must be declared a var.

Custom Getter

A good example of a custom accessor is a food label printed to display in the store.

class Food(
  val name: String,
  var price: String,
  var origin: String) {

  // 1
  val label: String
    get() {
      // 2
      val result = if (origin == "US") {
        "Local $name. Price: \$$price"
      } else {
        "$origin $name. Price: $price"
      // 3
      return result
fun main() {
  val tomato = Food("Tomato", "2.0", "US")
  println(tomato.label) // Local Tomato. Price: $2.0
  tomato.origin = "UK"
  println(tomato.label) // UK Tomato. Price: 2.0

Custom Setter

The property you wrote in the previous section is called a read-only property. It has a block of code to compute the value of the property: the custom getter.

// custom setter
var country: String
  // 2
  get() = "Country of origin: $origin"
  // 3
  set(value) {
    origin = value
  } = "Chile"
  println(tomato.label) // Chile Tomato. Price: 2.0
  println( // Country of origin: Chile

Companion Object Properties

You’ve explored how properties within a class are unique to each instance. Imagine two bowls of soup, each with its own temperature and ingredients. Companion objects offer a different approach. They let you define properties that belong to the class itself, shared by all instances – like a universal recipe for the perfect bowl of soup!

  // companion object property
  companion object {
    val maxDiscount = 0.3

 cucumber = Food("Cucumber", "1.0", "US")

  // Error: Unresolved reference
  // Can't access members of the companion object on an instance
  println(Food.maxDiscount) // 0.3
  // companion object property
  companion object {

  @JvmStatic val maxDiscount = 0.3

Food.getMaxDiscount(); // Fine, thanks to @JvmStatic
Food.Companion.getMaxDiscount(); // Fine too, and necessary if @JvmStatic were not used

Delegated Properties

So far, property initialization has been pretty basic: setting a value directly, using defaults, or calculating it with custom accessors. But what if you need more power? Delegated properties come to the rescue! These handy tools, introduced with the by keyword, let you offload property initialization or behavior to another object. Please see the next section of the lesson to learn more about by keyword with a real-life example.


Sometimes, a property might not have a value assigned immediately when a class instance is created. Maybe you’ll inject the value later, or it’s only needed under certain conditions. The lateinit keyword comes in handy here.

class Shopkeeper

class Shop {
  lateinit var keeper: Shopkeeper

val shop = Shop()
// ... shop has no shopkeeper, need to hire one!

// Error: kotlin.UninitializedPropertyAccessException:
// lateinit property keeper has not been initialized

// ... hired someone
shop.keeper = Shopkeeper()

Extension Properties

Say you bought a shop, but the business isn’t doing well, and you decided to do a sale for all products. The discount is 30%, and you need to update the prices of all your food items. It would be nice not to do the calculation of a new price every time you see a food item.

class Food(val name: String, val price: Int)
fun main() {
  val Food.newPrice: Double
   get() = 0.7 * price
  val pear = Food("Pear", 10)
  println(pear.newPrice) // 7.0
See forum comments
Download course materials from Github
Previous: Introduction Next: Demo