0
0
Android Kotlinmobile~15 mins

Sealed classes in Android Kotlin - Deep Dive

Choose your learning style9 modes available
Overview - Sealed classes
What is it?
Sealed classes in Kotlin are special classes that restrict which other classes can inherit from them. They let you define a fixed set of subclasses in one place. This helps the compiler know all possible types when you use them, making your code safer and easier to manage. Think of them as a closed family tree where only certain members are allowed.
Why it matters
Without sealed classes, developers often rely on open inheritance or enums that can't hold complex data. This can lead to bugs because the compiler can't check if all cases are handled, especially in when expressions. Sealed classes solve this by making the set of subclasses known and fixed, so the compiler can warn you if you miss a case. This reduces runtime errors and improves code clarity.
Where it fits
Before learning sealed classes, you should understand basic Kotlin classes, inheritance, and when expressions. After sealed classes, you can explore advanced Kotlin features like data classes, inline classes, and sealed interfaces. Sealed classes are often used in Android app state management and navigation logic.
Mental Model
Core Idea
Sealed classes create a closed set of subclasses so the compiler knows all possible types and can enforce exhaustive checks.
Think of it like...
Imagine a VIP club where only invited members can enter. The club owner knows exactly who is inside, so they can manage the group perfectly without surprises.
SealedClass
├── SubclassA
├── SubclassB
└── SubclassC

When expression on SealedClass covers all subclasses without else.
Build-Up - 7 Steps
1
FoundationBasic class inheritance in Kotlin
🤔
Concept: Understanding how classes inherit from each other in Kotlin.
In Kotlin, classes can inherit from other classes using the colon syntax. For example: open class Animal class Dog : Animal() Here, Dog inherits from Animal. This lets Dog use or override Animal's properties and functions.
Result
You can create a Dog object that is also an Animal.
Knowing inheritance is key because sealed classes build on this idea but restrict which classes can inherit.
2
FoundationUsing when expressions with classes
🤔
Concept: How to use when to check an object's type and act accordingly.
The when expression lets you run code based on the type or value of an object: fun describe(animal: Animal) = when(animal) { is Dog -> "It's a dog" else -> "Unknown animal" } This checks the type and returns a string.
Result
You get different outputs depending on the object's type.
When expressions are powerful for branching logic but can miss cases if the set of types is open.
3
IntermediateIntroducing sealed classes
🤔Before reading on: do you think sealed classes allow any subclass anywhere or only specific ones? Commit to your answer.
Concept: Sealed classes restrict subclassing to a fixed set defined in the same file.
Declare a sealed class with the sealed keyword: sealed class Result class Success(val data: String) : Result() class Error(val error: Throwable) : Result() Only Success and Error can inherit Result, and they must be in the same file.
Result
The compiler knows all subclasses of Result and can check when expressions exhaustively.
Understanding sealed classes limits inheritance and enables safer, clearer branching logic.
4
IntermediateExhaustive when with sealed classes
🤔Before reading on: do you think the compiler warns if you miss a subclass in when with sealed classes? Commit to your answer.
Concept: When expressions on sealed classes must cover all subclasses or the compiler warns.
Using the sealed class Result: fun handle(result: Result) = when(result) { is Success -> println("Data: ${result.data}") is Error -> println("Error: ${result.error.message}") } If you omit a subclass, the compiler shows an error or warning.
Result
You get safer code because you can't forget to handle a case.
Knowing this prevents bugs caused by missing cases in conditional logic.
5
IntermediateSealed classes with data and object subclasses
🤔
Concept: Sealed subclasses can be data classes or objects to hold data or single instances.
You can define subclasses like: sealed class UiState object Loading : UiState() data class Success(val content: String) : UiState() data class Error(val message: String) : UiState() This lets you represent states with or without data.
Result
You model complex states cleanly and safely.
Understanding this helps you design clear and concise state representations.
6
AdvancedSealed classes and file boundaries
🤔Before reading on: do you think sealed subclasses can be in different files? Commit to your answer.
Concept: Sealed subclasses must be declared in the same file as the sealed class, limiting extension scope.
Kotlin requires all subclasses of a sealed class to be in the same file. This keeps the set closed and known at compile time. You cannot add subclasses elsewhere.
Result
This enforces control over inheritance and helps the compiler with exhaustive checks.
Knowing this prevents accidental open inheritance and keeps your codebase predictable.
7
ExpertSealed interfaces and future Kotlin changes
🤔Before reading on: do you think sealed interfaces behave exactly like sealed classes? Commit to your answer.
Concept: Kotlin now supports sealed interfaces, which combine sealed restrictions with interface flexibility.
Sealed interfaces let you define a closed set of implementations but allow multiple inheritance: sealed interface Event class Click : Event class Swipe : Event This is useful for more flexible designs and is a newer Kotlin feature.
Result
You get sealed behavior with interface benefits like multiple inheritance.
Understanding sealed interfaces helps you write more flexible and modern Kotlin code.
Under the Hood
At compile time, Kotlin tracks all subclasses of a sealed class because they must be declared in the same file. This allows the compiler to know the complete set of subclasses. When you use a when expression on a sealed class, the compiler checks if all subclasses are covered. If not, it warns or errors. This is possible because the inheritance is closed and fixed, unlike open classes where subclasses can be anywhere.
Why designed this way?
Sealed classes were designed to improve safety and clarity in Kotlin by restricting inheritance to a known set. This avoids errors from missing cases in conditional logic and makes code easier to maintain. The design trades off some flexibility for stronger compile-time guarantees. Alternatives like enums are limited to simple values, so sealed classes fill the gap for complex hierarchies.
SealedClass (sealed)
│
├─ SubclassA
│
├─ SubclassB
│
└─ SubclassC

Compiler knows all subclasses here → Exhaustive when checks

OpenClass (open)
│
├─ SubclassX (any file)
│
├─ SubclassY (any file)
│
└─ SubclassZ (any file)

Compiler cannot guarantee all subclasses → No exhaustive checks
Myth Busters - 4 Common Misconceptions
Quick: Can sealed classes have subclasses declared in other files? Commit yes or no.
Common Belief:Sealed classes can have subclasses anywhere in the project, just like open classes.
Tap to reveal reality
Reality:Sealed class subclasses must be declared in the same file as the sealed class.
Why it matters:If you assume subclasses can be anywhere, you might miss cases in when expressions, leading to runtime bugs.
Quick: Do sealed classes behave exactly like enums? Commit yes or no.
Common Belief:Sealed classes are just like enums but with more features.
Tap to reveal reality
Reality:Sealed classes can hold complex data and have multiple properties, unlike enums which are simple constants.
Why it matters:Confusing them leads to poor design choices, like using enums when you need richer data structures.
Quick: Does the compiler always force you to handle all sealed subclasses in when expressions? Commit yes or no.
Common Belief:The compiler always forces exhaustive when checks on sealed classes.
Tap to reveal reality
Reality:Exhaustive checks happen only if the when expression is used as an expression (returns a value), not as a statement.
Why it matters:Assuming always exhaustive can cause missed cases if when is used as a statement, leading to bugs.
Quick: Can sealed classes be instantiated directly? Commit yes or no.
Common Belief:You can create objects directly from a sealed class.
Tap to reveal reality
Reality:Sealed classes are abstract by nature and cannot be instantiated directly; only their subclasses can.
Why it matters:Trying to instantiate sealed classes causes compile errors and confusion about their purpose.
Expert Zone
1
Sealed classes improve performance in when expressions because the compiler can optimize based on the fixed subclass set.
2
Using sealed interfaces allows combining sealed restrictions with multiple inheritance, which sealed classes alone cannot do.
3
The requirement that subclasses be in the same file can be bypassed in Kotlin 1.5+ with sealed interfaces and inline classes, offering more flexibility.
When NOT to use
Avoid sealed classes when you need open inheritance or want to allow third-party extensions. Use open classes or interfaces instead. Also, if your hierarchy is very large or spread across many files, sealed classes can become cumbersome.
Production Patterns
Sealed classes are widely used in Android for representing UI states, navigation events, and result types. They enable safe state machines and reduce bugs by forcing exhaustive handling. Combining sealed classes with Kotlin coroutines and Flow is common for reactive UI updates.
Connections
Algebraic Data Types (ADTs)
Sealed classes implement the concept of sum types, a core part of ADTs in functional programming.
Understanding sealed classes helps grasp ADTs, which are used in many languages to model data with fixed variants.
State Machines
Sealed classes are often used to represent states and transitions in state machines.
Knowing sealed classes clarifies how to model states explicitly and safely in mobile apps and other systems.
Access Control in Security
Sealed classes restrict subclassing like access control restricts resource usage.
Recognizing sealed classes as a form of controlled access helps understand design patterns that limit extension for safety.
Common Pitfalls
#1Forgetting to handle all subclasses in a when expression.
Wrong approach:fun handle(result: Result) = when(result) { is Success -> println("Success") } // Missing Error case, no else branch
Correct approach:fun handle(result: Result) = when(result) { is Success -> println("Success") is Error -> println("Error") }
Root cause:Not realizing the compiler requires exhaustive handling of all sealed subclasses to avoid runtime errors.
#2Declaring sealed subclasses in different files.
Wrong approach:// File1.kt sealed class Result // File2.kt class Success : Result() // Error: subclass not in same file
Correct approach:// File1.kt sealed class Result class Success : Result() class Error : Result()
Root cause:Misunderstanding Kotlin's rule that sealed subclasses must be in the same file as the sealed class.
#3Trying to instantiate a sealed class directly.
Wrong approach:val r = Result() // Error: Cannot create an instance of sealed class
Correct approach:val r = Success("data") // Correct: instantiate subclass
Root cause:Confusing sealed classes with regular classes; sealed classes are abstract and cannot be instantiated.
Key Takeaways
Sealed classes restrict inheritance to a fixed set of subclasses declared in the same file, enabling safer and clearer code.
They allow the compiler to check that all cases are handled in when expressions, reducing runtime errors.
Sealed subclasses can be data classes or objects, letting you model complex states with or without data.
Sealed interfaces extend this concept with more flexibility, supporting multiple inheritance.
Understanding sealed classes helps you write robust Android apps with explicit state management and safer branching logic.