Skip to content

Beginner: Kotlin · Classes, objects and collections · topic 10 of 50 · 30 min

Data, enum and sealed classes, and objects

Explanation

A data class holds data and gets equals, hashCode, toString, copy and destructuring for free. An enum class lists a fixed set of constants. A sealed class or sealed interface lists a fixed set of subtypes that can carry different data, which is perfect for screen states, and when on it needs no else. object creates a single instance (a singleton); a companion object holds class-level members.

Data classes

Many classes just hold data: a Product, a Contact, an API response. For these, mark the class with data. Kotlin then writes useful functions for you, based on the properties in the primary constructor.

kotlin
data class Product(val id: Int, val name: String, val price: Int)

fun main() {
    val a = Product(1, "Kurta", 899)
    val b = Product(1, "Kurta", 899)
    println(a)              // Product(id=1, name=Kurta, price=899)
    println(a == b)         // true: compares the data, not the memory address
    val sale = a.copy(price = 699)     // a new object with one change
    val (id, name) = sale              // destructuring
    println("$id $name ${sale.price}")  // 1 Kurta 699
}
You get toString, equals/hashCode, copy and componentN (destructuring) for free.

copy is important in Android: UI state is usually held in immutable data classes, and you create a changed version with copy instead of changing the original. That makes it easy for the UI to notice changes.

Only properties declared in the primary constructor take part in these generated functions. A data class must have at least one such property, and it cannot be open, abstract or sealed.

Enum classes

kotlin
enum class PaymentMode(val label: String) {
    UPI("UPI"), CARD("Card"), CASH("Cash on delivery");

    fun isOnline() = this != CASH
}

fun main() {
    val m = PaymentMode.UPI
    println(m.label)          // UPI
    println(m.isOnline())     // true
    println(PaymentMode.entries.size)   // 3
}
Enums list a fixed set of constants. They can have properties and functions. entries lists them all.

Sealed classes and interfaces

An enum constant cannot carry different data for each case. A sealed class (or sealed interface) is a closed family of types: all its subtypes are known to the compiler, and each can hold its own data. Combined with when, the compiler checks that you handled every case, so you do not need else.

kotlin
sealed interface UiState {
    data object Loading : UiState
    data class Success(val items: List<String>) : UiState
    data class Error(val message: String) : UiState
}

fun render(state: UiState): String = when (state) {
    UiState.Loading -> "Showing spinner"
    is UiState.Success -> "Showing ${state.items.size} items"
    is UiState.Error -> "Error: ${state.message}"
}   // no else needed: the compiler knows all cases
This exact pattern is used in most modern Android screens.

If someone later adds a new state, such as Empty, every when that forgot it stops compiling. That is a big safety win in large apps.

object and companion object

object declares a class and its single instance at the same time: a singleton. Use it for things there should be only one of, like app-wide constants. A companion object lives inside a class and holds members that belong to the class itself rather than to each object, similar to static members in Java; it is often used for factory functions and constants.

kotlin
object AppConfig {
    const val BASE_URL = "https://api.example.com/"
    const val TIMEOUT_SECONDS = 30
}

class User private constructor(val phone: String) {
    companion object {
        fun fromInput(raw: String): User? {
            val digits = raw.filter { it.isDigit() }.takeLast(10)
            return if (digits.length == 10) User(digits) else null
        }
    }
}

fun main() {
    println(AppConfig.BASE_URL)
    println(User.fromInput("+91 98765 43210")?.phone)   // 9876543210
}
const val is a compile-time constant. The private constructor forces everyone to use fromInput, which validates.
NeedUse
Hold data, compare by value, copy with changesdata class
A fixed list of simple optionsenum class
A fixed set of cases with different datasealed class / sealed interface
Exactly one instanceobject
Class-level helpers and constantscompanion object

Common mistakes

  • Using an enum for states that need different data (Error with a message). Use a sealed interface.
  • Adding else to a when over a sealed type: it hides missing cases. Leave it out so the compiler can help.
  • Putting var properties in data classes used as UI state. Prefer val and copy.
  • Using object for things that should have many instances.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Data, enum and sealed classes, and objects · 1/5

data class Product(val id: Int, val name: String)

A data class holds data. Kotlin writes to string, equals, copy and destructuring for you.

Prefer a video by a teacher? Find free videos on “kotlin data class sealed class enum object companion” on YouTube ↗

In real life: Order status in a delivery app

A food delivery app models each order's status as a sealed interface: Placed, Preparing(minutesLeft), OutForDelivery(riderName, phone), Delivered, Cancelled(reason). The status screen uses one when, with no else. When the team adds "ReadyForPickup", Android Studio immediately shows every screen that must handle it.

kotlin
sealed interface OrderStatus {
    data object Placed : OrderStatus
    data class Preparing(val minutesLeft: Int) : OrderStatus
    data class OutForDelivery(val rider: String) : OrderStatus
    data class Cancelled(val reason: String) : OrderStatus
}

fun message(s: OrderStatus) = when (s) {
    OrderStatus.Placed -> "Order placed"
    is OrderStatus.Preparing -> "Ready in ${s.minutesLeft} min"
    is OrderStatus.OutForDelivery -> "${s.rider} is on the way"
    is OrderStatus.Cancelled -> "Cancelled: ${s.reason}"
}

fun main() = println(message(OrderStatus.Preparing(12)))   // Ready in 12 min
Each state carries exactly the data it needs.

Practice (0/3 done)

Task 1 · Question

What does this print? data class P(val x: Int, val y: Int) fun main() { val a = P(1, 2) val b = a.copy(y = 5) println("$a $b ${a == b}") }

Task 2 · Question

A screen can be Loading, show a list of items, or show an error message. Which type fits best?

Task 3 · Practice

Model a UPI payment: data class Payment(val id: String, val amountPaise: Long, val status: PayStatus) where PayStatus is a sealed interface with Pending, Success(val utr: String) and Failed(val reason: String). Write fun describe(p: Payment): String using when without else, and create a failed copy of a pending payment with copy.

Quick quiz

Question 1

1.What does == compare for two data class objects?

Question 2

2.Why can a when over a sealed type skip else?

Question 3

3.What is an object declaration?

Question 4

4.Which is like Java's static members?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does == compare for two data class objects?

Answer: Their property values

Data classes generate equals based on properties.

Why can a when over a sealed type skip else?

Answer: The compiler knows every subtype and checks all are handled

Exhaustiveness is checked at compile time.

What is an object declaration?

Answer: A class with exactly one instance

object creates a singleton.

Which is like Java's static members?

Answer: companion object

Companion object members are accessed through the class name.

Where can subclasses of a sealed class be declared?Related

Answer: Only in the same library (file)

That is what makes the set known.

From Dart 3: enums, records, patterns and sealed classes

What does sealed interface Payment permits Upi, Card {} do?Related

Answer: Allows only Upi and Card to implement Payment

Sealed types restrict their implementations.

From Interfaces

Default Object.equals behaves like…Related

Answer: ==, same object only

Without overriding, equals checks identity.

From toString, equals, hashCode and records

When is a sealed interface better than an enum?Related

Answer: When cases carry different data

Subtypes can hold their own data.

From Interview questions: Kotlin

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Classes, objects and collections” before its exam: Inheritance and interfaces, Collections: List, Set and Map, Collection operations.
  2. Module 3 exam: Classes, objects and collections · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Coroutines and Flow. 50 topics left in the course before the final exam.

← Previous: Classes and objects