Kotlin Sealed Class

A Kotlin sealed class represents a restricted class hierarchy. A value of the sealed parent type can be one of a known set of subtypes, and those direct subtypes are known to the compiler at compile time.

Sealed classes are useful when a program has a limited set of possible cases, but each case may still need its own data and behavior. Common examples include operation types, UI state, API results, screen events, and error models.

Kotlin Sealed Class Example with ArithmeticOperation

Following is an example of sealed class and subclasses that extend the sealed class.

</>
Copy
sealed class ArithmeticOperation

class Add(var a: Int, var b: Int): ArithmeticOperation()
class Subtract(var a: Int, var b: Int): ArithmeticOperation()
class Multiply(var a: Int, var b: Int): ArithmeticOperation()
class Divide(var a: Int, var b: Int): ArithmeticOperation()

A value of type ArithmeticOperation is restricted to Add, Subtract, Multiply and Divide. Code that receives an ArithmeticOperation can handle these known possibilities using a when expression.

This situation may look similar to that of Kotlin Enum. But the difference between Enum and Sealed Classes is that, in Enum there is only one value for each class type, whereas in Sealed Class, there could be multiple object instances for a class type (whose parent is a sealed class).

Kotlin Sealed Class Syntax

The basic syntax is to add the sealed modifier before the class declaration, and then declare the allowed direct subclasses in the same package and module.

</>
Copy
sealed class Result

data class Success(val data: String) : Result()
data class Failure(val message: String) : Result()
object Loading : Result()

In this syntax, Success, Failure, and Loading are the possible direct subclasses of Result. Use a data class when a case carries values, and use an object or data object when a case has only one instance and does not need constructor data.

Kotlin Sealed Class Rules for Subclasses and Constructors

  • Restricted hierarchy: A sealed class has a known set of direct subclasses.
  • Direct subclass location: Direct subclasses must be declared in the same package and module. In modern Kotlin, they do not have to be in the same file.
  • Named subclasses: Direct subclasses must have proper qualified names. They cannot be local classes or anonymous objects.
  • Constructors: A sealed class constructor is protected by default. It may also be declared private, but it cannot be declared public or internal.
  • Abstract nature: A sealed class is abstract by design, so it cannot be instantiated directly.
  • Indirect subclasses: If a direct subclass is not sealed, its own subclasses may extend it according to normal Kotlin inheritance rules.

Example 1 – Kotlin Sealed Class with when expression

In this example, we have a sealed class ArithmeticOperation with four sub-classes: Add, Subtract, Multiply and Divide. We will use when expression to demonstrating the usage of Sealed Class.

Kotlin Program – example.kt

</>
Copy
fun main(args: Array<String>) {
    var a = 4
    var b = 3
    var operation1 = Add(a, b)
    var result1 = execute(operation1)
    println("Addition : "+result1)

    var operation2 = Subtract(a, b)
    var result2 = execute(operation2)
    println("Subtraction : "+result2)

    var operation3 = Multiply(a, b)
    var result3 = execute(operation3)
    println("Multiplication : "+result3)

    var operation4 = Divide(a, b)
    var result4 = execute(operation4)
    println("Division : "+result4)
}

sealed class ArithmeticOperation

class Add(var a: Int, var b: Int): ArithmeticOperation()
class Subtract(var a: Int, var b: Int): ArithmeticOperation()
class Multiply(var a: Int, var b: Int): ArithmeticOperation()
class Divide(var a: Int, var b: Int): ArithmeticOperation()

fun execute(op: ArithmeticOperation) = when (op) {
    is Add -> op.a + op.b
    is Subtract -> op.a - op.b
    is Multiply -> op.a * op.b
    is Divide -> op.a / op.b
}

This example could be considered a special case, where Kotlin When is used as an expression. In the execute() function, Kotlin Compiler makes sure that all the classes from the allowed sub-class set are addressed.

Output

Addition : 7
Subtraction : 1
Multiplication : 12
Division : 1

Why Kotlin when Works Well with Sealed Classes

The main benefit of using a sealed class with when is exhaustive checking. Since the compiler knows the direct subclasses, a when expression can cover every case without requiring an else branch.

If you later add a new subclass, Kotlin can report the missing branch in the when expression. This makes sealed classes safer than using plain strings, integer codes, or loosely related classes for a fixed set of cases.

</>
Copy
sealed class NetworkResult {
    data class Success(val body: String) : NetworkResult()
    data class Error(val code: Int, val message: String) : NetworkResult()
    object Loading : NetworkResult()
}

fun message(result: NetworkResult): String {
    return when (result) {
        is NetworkResult.Success -> result.body
        is NetworkResult.Error -> "Error ${result.code}: ${result.message}"
        NetworkResult.Loading -> "Loading"
    }
}

In the example above, the when expression handles all three possible cases: Success, Error, and Loading. No else branch is needed because the sealed hierarchy is complete.

Kotlin Sealed Class for Type-Safe UI State

Sealed classes are often used to model UI state because a screen is usually in one of a few clear states: loading, showing content, showing an empty view, or showing an error.

</>
Copy
sealed class UserScreenState {
    object Loading : UserScreenState()
    data class Content(val userName: String) : UserScreenState()
    object Empty : UserScreenState()
    data class Error(val message: String) : UserScreenState()
}

fun render(state: UserScreenState) {
    when (state) {
        UserScreenState.Loading -> println("Show progress")
        is UserScreenState.Content -> println("Show user: ${state.userName}")
        UserScreenState.Empty -> println("Show empty state")
        is UserScreenState.Error -> println("Show error: ${state.message}")
    }
}

This approach avoids spreading separate Boolean flags such as isLoading, hasError, and hasData across the code. The sealed class keeps the allowed states in one hierarchy.

Kotlin Sealed Class vs Enum Class

Use an enum class when each option is a simple named constant. Use a sealed class when different cases need different properties, constructors, or behavior.

ComparisonKotlin enum classKotlin sealed class
Best useFixed set of simple constantsFixed set of related types
Data per caseSame structure for every enum constantEach subclass can have its own constructor data
InstancesOne instance per enum constantMultiple instances are possible for class subclasses
ExampleLOW, MEDIUM, HIGHSuccess(data), Error(message), Loading

Kotlin Sealed Class vs Sealed Interface

Kotlin also supports sealed interfaces. Both sealed classes and sealed interfaces restrict direct implementations to a known hierarchy, but they are suited to slightly different designs.

NeedPrefer sealed classPrefer sealed interface
Shared constructor stateYes, a sealed class can define constructor parameters and common state.No, interfaces do not hold constructor state.
Common implementationUseful when subclasses should inherit common code.Useful when types only need to promise a common contract.
Multiple type rolesLimited because a class can extend only one class.Useful because a class can implement multiple interfaces.
Typical exampleAPI result types with shared behavior.Events or capabilities that different classes may implement.

When to Use Kotlin Sealed Classes

  • Use a sealed class when the possible cases are known and should not be extended freely by unrelated code.
  • Use it when every case may carry different data, such as Success(data) and Error(message).
  • Use it with when expressions when you want the compiler to help check that all cases are handled.
  • Use it for domain states such as payment status, login result, screen state, or validation result.
  • Avoid using a sealed class for open hierarchies where third-party or future modules should add new subtypes freely.

Common Mistakes with Kotlin Sealed Classes

  • Expecting old same-file rules in modern Kotlin: Direct subclasses now need to be in the same package and module, not necessarily the same file.
  • Using sealed class for simple constants: If each case is only a name, an enum class may be simpler.
  • Adding an unnecessary else branch: If all sealed subclasses are handled, an else branch can hide missing-case errors when new subclasses are added.
  • Using class instead of object for single-instance cases: Use object or data object for cases such as Loading or Empty.
  • Forgetting that subclasses can contain data: A sealed class is more useful when each case can model its own values clearly.

Kotlin Sealed Class QA Checklist

  • Check that the sealed parent name clearly describes the closed hierarchy, such as Result, ScreenState, or ArithmeticOperation.
  • Confirm that direct subclasses are declared in the same package and module as the sealed parent.
  • Use data class for cases that carry values and object or data object for cases without per-instance data.
  • Review every when expression and avoid an else branch when exhaustive handling is clearer.
  • Compare with enum class before choosing sealed class for cases that are only simple constants.
  • Choose sealed interface instead of sealed class when the hierarchy should represent a contract without shared constructor state.

FAQs on Kotlin Sealed Class

What does a sealed class do in Kotlin?

A sealed class in Kotlin restricts inheritance to a known set of direct subclasses. This lets the compiler understand all possible cases of the sealed parent type and helps with exhaustive when expressions.

How do you use a sealed class in Kotlin?

Declare a parent class with the sealed modifier, define its allowed subclasses in the same package and module, and then handle the sealed parent type using a when expression or normal type checks.

When should I use sealed class instead of enum class in Kotlin?

Use a sealed class when each case needs different data or behavior. Use an enum class when the cases are simple constants with the same structure.

When should I use sealed class vs sealed interface in Kotlin?

Use a sealed class when the hierarchy needs shared constructor state or common implementation. Use a sealed interface when the hierarchy represents a contract and classes may need to implement multiple roles.

Can a Kotlin sealed class be instantiated directly?

No. A sealed class is abstract by design and cannot be instantiated directly. You create instances of its subclasses, such as a data class subclass or an object subclass.

Kotlin Sealed Class Summary

In this Kotlin Tutorial, we learned that sealed classes model a closed set of related types. They are especially useful with when expressions, where Kotlin can check whether all known subclasses are handled. Use sealed classes when different cases need different data or behavior, and use enum classes for simple named constants.