Skip to content
All posts
September 14, 20266 min read

Kotlin Enum vs Sealed Class: Which One Should You Pick

A practical breakdown of Kotlin Enum and Sealed Class, when to use each, and how to decide based on your app's architecture needs.

AndroidKotlinArchitectureBest Practices
Share:

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.

The Problem Both Solve

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:

code
enum class
and
code
sealed class
. Both restrict what values a variable can hold. Both work with
code
when
. But the tradeoffs are significant, and most teams pick one by habit rather than intent.

This post breaks down exactly how they differ, where each shines, and how to choose with confidence in real Android projects.

Kotlin Enums: Strengths and Limitations

Enums are the simpler tool. Each constant is a single instance with a fixed name and optional properties.

kotlin
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

code
values()
,
code
valueOf()
, and ordinal support out of the box. Iteration is trivial — loop through
code
OrderStatus.values()
to populate a spinner or database lookup table.

[!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:

kotlin
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: Strengths and Limitations

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

code
data class
or
code
object
.

kotlin
sealed 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

code
Result.Success
carries data. A
code
Result.Error
carries an exception. A
code
Result.Loading
carries nothing. The compiler knows every possible subtype, so
code
when
expressions are exhaustive without an
code
else
branch.

Sealed 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

code
sealed
in the same module. This restricts where you can define subtypes, which matters in multi-module projects. Enums have no such constraint — they work everywhere without module boundaries.

Side-by-Side Comparison

CriteriaEnumSealed Class
Syntax complexityMinimalModerate
Per-constant dataRequires abstract propertiesNative via constructor params
Per-constant behaviorAbstract methods (verbose)Direct override (clean)
Memory footprintOne class + N objectsOne class + N subtypes
Reflection supportBuilt-in (
code
values()
,
code
valueOf()
)
None
Multi-module supportFully supportedSame-module only (non-local)
Exhaustive
code
when
YesYes
Data class supportNoYes
DEX method countLowerHigher 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.

Real-World Example: Payment Processing

Consider a payment screen in an Android app. The user can select a method, and each method requires different input data.

kotlin
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

code
PaymentOption
and handles each case distinctly:

kotlin
fun 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

code
else
branch. The compiler enforces coverage. Adding a new option like
code
PaymentOption.GooglePay
forces you to update every
code
when
expression — exactly the safety you want.

If 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 (

code
UiState.Loading
,
code
UiState.Content
,
code
UiState.Error
) because each state carries exactly the data the composable needs.

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.

How to Decide

Ask yourself three questions in order:

  1. Does each variant carry different data? If yes, use a sealed class.
  2. Do you need to iterate or reflect over all values at runtime? If yes, use an enum.
  3. Are all variants behaviorally identical with only a name or simple value? If yes, use an enum.

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.

Key Takeaways

  • Pick Sealed Classes when variants have different fields or behavior — they give you type-safe, exhaustive
    code
    when
    expressions with clean data carrying per subtype.
  • Pick Enums when the set is fixed, behaviorally identical, and you need
    code
    values()
    or
    code
    valueOf()
    for reflection, iteration, or simple label mapping.
  • Avoid adding abstract methods to Enums with many constants — each one generates an anonymous subclass and inflates your DEX count unnecessarily.
  • In Jetpack Compose and MVVM, prefer Sealed Classes for
    code
    UiState
    and
    code
    Result
    wrappers because each state naturally holds different data for the composable to render.
  • Always check module boundaries — Sealed Classes restrict subtypes to the same module unless you use
    code
    sealed interface
    across package declarations carefully.
  • Test every
    code
    when
    expression over a sealed hierarchy with a unit test that asserts all cases are covered, especially after adding a new subtype.
Share:
S

Sudarshan 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.

Stay updated

Get new posts on Android, Kotlin, and solo dev straight to your inbox.

Newsletter preferences

Related Apps

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.

Building something? Available for Android dev and QA consulting.

Work with me

Comments — powered by Giscus

Apps tagged with this