MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
A practical breakdown of Kotlin Enum and Sealed Class, when to use each, and how to decide based on your app's architecture needs.
On this page
Kotlin Enums and Sealed Classes both model restricted type hierarchies — but they differ in flexibility, memory behavior, and pattern-matching power. Pick the wrong one and you will fight your own code every time you add a new variant.
When modeling a fixed set of states, operations, or configurations in Kotlin, you need a type that guarantees exhaustiveness. The compiler should catch every case so that new values cannot slip through untested. Kotlin gives you two tools:
enum classsealed classwhenThis post breaks down exactly how they differ, where each shines, and how to choose with confidence in real Android projects.
Enums are the simpler tool. Each constant is a single instance with a fixed name and optional properties.
enum class OrderStatus(val label: String) {
PENDING("Awaiting confirmation"),
SHIPPED("In transit"),
DELIVERED("Completed"),
CANCELLED("Refund issued");
}Enums compile to a single class with static constants. You get built-in
values()valueOf()OrderStatus.values()[!TIP] Use Enums when your set of values is truly fixed, each value carries only data (no behavior), and you need ordinal or name-based reflection.
Where Enums fall short is behavior. Every constant shares the same class body. You cannot override a function per constant without wrapping it in an abstract method, which forces every constant to implement it:
enum class PaymentMethod {
CREDIT_CARD {
override fun validate(): Boolean = true
},
PAYPAL {
override fun validate(): Boolean = true
},
CRYPTO {
override fun validate(): Boolean = false
};
abstract fun validate(): Boolean
}This works, but the syntax is verbose. Each constant becomes an anonymous subclass behind the scenes, which increases bytecode size — a real concern in apps with many Enums.
[!WARNING] Enums with abstract methods create one anonymous subclass per constant. If you have 20 constants, you have 20 extra classes in your DEX file.
Enums also cannot hold different data per constant without pairing it with an abstract property, which adds boilerplate. They are value objects — simple, predictable, and limited.
Sealed Classes give you the same restricted hierarchy but with full class power. Each subtype can carry different data, override different behavior, and even be a
data classobjectsealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
data object Loading : Result<Nothing>()
}Look at that. Three subtypes, each with completely different fields and types. A
Result.SuccessResult.ErrorResult.LoadingwhenelseSealed Classes also work naturally with generics, inheritance hierarchies, and state-holding patterns in Jetpack Compose and MVVM architectures.
[!IMPORTANT] Sealed Classes are the default choice when each variant has different data or different behavior. They scale cleanly as your app grows.
The tradeoff is visibility. A sealed class must be either declared in the same file (for non-local declarations) or marked
sealed| Criteria | Enum | Sealed Class |
|---|---|---|
| Syntax complexity | Minimal | Moderate |
| Per-constant data | Requires abstract properties | Native via constructor params |
| Per-constant behavior | Abstract methods (verbose) | Direct override (clean) |
| Memory footprint | One class + N objects | One class + N subtypes |
| Reflection support | Built-in ( code code | None |
| Multi-module support | Fully supported | Same-module only (non-local) |
| Exhaustive code | Yes | Yes |
| Data class support | No | Yes |
| DEX method count | Lower | Higher with many subtypes |
Use this table as a quick reference when you are starting a new model. If your variant needs data or behavior, go sealed. If you need reflection or iteration, go enum.
Consider a payment screen in an Android app. The user can select a method, and each method requires different input data.
sealed class PaymentOption {
data class Card(
val cardNumber: String,
val expiry: String,
val cvv: String
) : PaymentOption()
data class PayPal(val email: String) : PaymentOption()
data object CashOnDelivery : PaymentOption()
}With this structure, your ViewModel accepts a
PaymentOptionfun processPayment(option: PaymentOption): String = when (option) {
is PaymentOption.Card -> "Charging ${option.cardNumber}"
is PaymentOption.PayPal -> "Redirecting to ${option.email}"
is PaymentOption.CashOnDelivery -> "Cash on delivery"
}No
elsePaymentOption.GooglePaywhenIf you attempted this with an Enum, you would need an abstract property for every possible data shape, which quickly becomes unmaintainable.
[!NOTE] In MVVM architectures, sealed classes are ideal for UI states (
,codeUiState.Loading,codeUiState.Content) because each state carries exactly the data the composable needs.codeUiState.Error
For something like logging levels or network retry policies — where every value is behaviorally identical and carries only a label or integer — Enums remain the better fit. The simplicity is genuine, not forced.
Ask yourself three questions in order:
Most modeling tasks in modern Android apps — navigation routes, UI states, network results, feature flags — involve variants with different data. That points strongly toward sealed classes. Reserve enums for genuinely simple, fixed sets where reflection or iteration matters.
whenvalues()valueOf()UiStateResultsealed interfacewhenSudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
ReadPrivate dream journal — structured entry capture, pattern tagging, and optional Claude-powered insight generation. All data stays on-device by default.
ReadWorkout tracker — exercise logging with set/rep/weight history, goal progression, and local Room DB persistence. No account, no cloud sync required.
Read