Enums are perfect when a variable should only hold one of a fixed set of values — days of the week, directions, order status, themes. Kotlin's enum classes go far beyond simple named constants — they can have properties, functions, and implement interfaces. This guide covers everything with practical Android examples.


What Is an Enum?

An enum (enumeration) is a type that represents a group of named constants. Instead of using raw strings or integers to represent fixed values, you use an enum — making your code safer, clearer, and less error-prone.

// ❌ Using raw strings — easy to make typos, no type safety
var status = "active"
if (status == "actve") { }  // typo — no compile error

// ✅ Using enum — type safe, compiler catches mistakes
enum class UserStatus { ACTIVE, INACTIVE, BANNED, PENDING }

var status = UserStatus.ACTIVE
if (status == UserStatus.ACTVE) { }  // ❌ compile error — typo caught

Declaring an Enum Class

enum class Direction {
    NORTH,
    SOUTH,
    EAST,
    WEST
}

val heading = Direction.NORTH
println(heading)          // NORTH
println(heading.name)     // NORTH — name as String
println(heading.ordinal)  // 0 — position in declaration (0-based)

Using Enum with when

Enums work perfectly with when — and since the compiler knows all possible values, you get exhaustive checking:

enum class Direction { NORTH, SOUTH, EAST, WEST }

fun describe(direction: Direction): String {
    return when (direction) {
        Direction.NORTH -> "Heading up"
        Direction.SOUTH -> "Heading down"
        Direction.EAST  -> "Heading right"
        Direction.WEST  -> "Heading left"
        // No else needed — all cases covered
    }
}

println(describe(Direction.NORTH))  // Heading up

If you add a new value to the enum later and forget to handle it in when, the compiler warns you — just like sealed classes.


Enum with Properties

Each enum constant can carry data by giving the enum class a constructor:

enum class Planet(val mass: Double, val radius: Double) {
    MERCURY(3.303e+23, 2.4397e6),
    VENUS(4.869e+24, 6.0518e6),
    EARTH(5.976e+24, 6.37814e6),
    MARS(6.421e+23, 3.3972e6);

    val gravity: Double
        get() = 6.67300E-11 * mass / (radius * radius)
}

println(Planet.EARTH.mass)      // 5.976E24
println(Planet.EARTH.gravity)   // 9.802652743...
println(Planet.MARS.gravity)    // 3.709208..

Practical Android example — Theme enum with colors:

enum class AppTheme(
    val backgroundColor: Int,
    val textColor: Int,
    val accentColor: Int,
    val displayName: String
) {
    LIGHT(
        backgroundColor = 0xFFFFFFFF.toInt(),
        textColor = 0xFF000000.toInt(),
        accentColor = 0xFF6200EE.toInt(),
        displayName = "Light"
    ),
    DARK(
        backgroundColor = 0xFF121212.toInt(),
        textColor = 0xFFFFFFFF.toInt(),
        accentColor = 0xFFBB86FC.toInt(),
        displayName = "Dark"
    ),
    SYSTEM(
        backgroundColor = 0xFF000000.toInt(),
        textColor = 0xFFFFFFFF.toInt(),
        accentColor = 0xFF6200EE.toInt(),
        displayName = "Follow System"
    );
}

// Usage
val theme = AppTheme.DARK
println(theme.displayName)   // Dark
println(theme.textColor)     // -1 (white as Int)

Enum with Functions

Enum classes can define functions — both shared (same for all constants) and abstract (each constant implements its own):

Shared Function

enum class DayOfWeek {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;

    fun isWeekend(): Boolean = this == SATURDAY || this == SUNDAY

    fun isWeekday(): Boolean = !isWeekend()

    fun next(): DayOfWeek {
        val values = entries
        return values[(ordinal + 1) % values.size]
    }
}

println(DayOfWeek.MONDAY.isWeekend())    // false
println(DayOfWeek.SATURDAY.isWeekend())  // true
println(DayOfWeek.FRIDAY.next())         // SATURDAY
println(DayOfWeek.SUNDAY.next())         // MONDAY

Abstract Function — Each Constant Has Its Own Implementation

enum class Operation(val symbol: String) {
    ADD("+") {
        override fun apply(a: Double, b: Double) = a + b
    },
    SUBTRACT("-") {
        override fun apply(a: Double, b: Double) = a - b
    },
    MULTIPLY("*") {
        override fun apply(a: Double, b: Double) = a * b
    },
    DIVIDE("/") {
        override fun apply(a: Double, b: Double) = a / b
    };

    abstract fun apply(a: Double, b: Double): Double

    override fun toString() = symbol
}

println(Operation.ADD.apply(10.0, 5.0))       // 15.0
println(Operation.MULTIPLY.apply(4.0, 3.0))   // 12.0
println(Operation.DIVIDE.apply(10.0, 2.0))    // 5.0
println("10 ${Operation.ADD} 5 = ${Operation.ADD.apply(10.0, 5.0)}")
// 10 + 5 = 15.0

Enum Implementing an Interface

interface Displayable {
    fun display(): String
}

enum class OrderStatus : Displayable {
    PENDING {
        override fun display() = "⏳ Pending"
    },
    PROCESSING {
        override fun display() = "🔄 Processing"
    },
    SHIPPED {
        override fun display() = "🚚 Shipped"
    },
    DELIVERED {
        override fun display() = "✅ Delivered"
    },
    CANCELLED {
        override fun display() = "❌ Cancelled"
    };
}

val status = OrderStatus.SHIPPED
println(status.display())   // 🚚 Shipped

Enum Utility Functions

values() and entries — Get All Constants

enum class Season { SPRING, SUMMER, AUTUMN, WINTER }

// entries (preferred in Kotlin 1.9+)
for (season in Season.entries) {
    println(season)
}
// SPRING, SUMMER, AUTUMN, WINTER

// values() — older approach, still works
for (season in Season.values()) {
    println(season)
}

println(Season.entries.size)   // 4

valueOf() — Get Enum from String

val season = Season.valueOf("SUMMER")
println(season)   // SUMMER

// Safe version — returns null instead of throwing
val safe = Season.entries.find { it.name == "FALL" }
println(safe)   // null — FALL doesn't exist

enumValues() and enumValueOf() — Generic Access

inline fun <reified T : Enum<T>> getAll(): List<T> {
    return enumValues<T>().toList()
}

println(getAll<Season>())  // [SPRING, SUMMER, AUTUMN, WINTER]

Real-World Android Examples

Network State

enum class NetworkState {
    CONNECTED,
    DISCONNECTED,
    CONNECTING,
    UNKNOWN;

    val isAvailable: Boolean
        get() = this == CONNECTED

    companion object {
        fun from(isConnected: Boolean): NetworkState {
            return if (isConnected) CONNECTED else DISCONNECTED
        }
    }
}

fun handleNetworkState(state: NetworkState) {
    when (state) {
        NetworkState.CONNECTED    -> hideOfflineBanner()
        NetworkState.DISCONNECTED -> showOfflineBanner("No internet connection")
        NetworkState.CONNECTING   -> showOfflineBanner("Connecting...")
        NetworkState.UNKNOWN      -> showOfflineBanner("Network status unknown")
    }
}

Sort Order

enum class SortOrder(val displayName: String, val apiParam: String) {
    NEWEST("Newest First", "created_at_desc"),
    OLDEST("Oldest First", "created_at_asc"),
    MOST_VIEWED("Most Viewed", "view_count_desc"),
    ALPHABETICAL("A to Z", "title_asc");

    companion object {
        val default = NEWEST
    }
}

// Build sort dropdown
val sortOptions = SortOrder.entries.map { it.displayName }
// ["Newest First", "Oldest First", "Most Viewed", "A to Z"]

// Use in API call
val currentSort = SortOrder.MOST_VIEWED
apiService.getArticles(sortBy = currentSort.apiParam)

Permission State

enum class PermissionState {
    GRANTED,
    DENIED,
    PERMANENTLY_DENIED,
    NOT_REQUESTED;

    val isGranted get() = this == GRANTED
    val shouldShowRationale get() = this == DENIED
    val shouldOpenSettings get() = this == PERMANENTLY_DENIED
}

fun handlePermission(state: PermissionState) {
    when (state) {
        PermissionState.GRANTED ->
            startCamera()
        PermissionState.DENIED ->
            showRationaleDialog("Camera permission is needed to take photos")
        PermissionState.PERMANENTLY_DENIED ->
            showSettingsDialog("Please enable camera permission in settings")
        PermissionState.NOT_REQUESTED ->
            requestCameraPermission()
    }
}

Article Category

enum class ArticleCategory(
    val displayName: String,
    val apiSlug: String,
    val iconResId: Int
) {
    KOTLIN("Kotlin", "kotlin", R.drawable.ic_kotlin),
    ANDROID("Android", "android", R.drawable.ic_android),
    JETPACK_COMPOSE("Jetpack Compose", "jetpack-compose", R.drawable.ic_compose),
    ARCHITECTURE("Architecture", "architecture", R.drawable.ic_architecture),
    DSA("DSA", "dsa", R.drawable.ic_dsa);

    companion object {
        fun fromSlug(slug: String): ArticleCategory? {
            return entries.find { it.apiSlug == slug }
        }
    }
}

// Usage
val category = ArticleCategory.fromSlug("kotlin")
println(category?.displayName)   // Kotlin

Enum vs Sealed Class — When to Use Which

This is a common question — here's the clear answer:

Use Enum When Use Sealed Class When
All values are the same shape Each case needs different data
Simple named constants Complex state with varying properties
You need values() / iteration You don't need to iterate
Ordering/comparison by ordinal No ordering needed
Using in when with simple behavior Using in when with type-specific data
// Enum — all values same shape, just named constants
enum class Direction { NORTH, SOUTH, EAST, WEST }
enum class LogLevel { DEBUG, INFO, WARNING, ERROR }

// Sealed class — each case has different data
sealed class NetworkResult {
    object Loading : NetworkResult()
    data class Success(val data: List<Article>) : NetworkResult()
    data class Error(val message: String, val code: Int) : NetworkResult()
}

Common Mistakes to Avoid

Mistake 1: Using raw strings/ints instead of enums

// ❌ Fragile — typos, no type safety
fun setTheme(theme: String) {
    if (theme == "dark") applyDark()
    else if (theme == "lite") applyLight()  // typo — no error
}

// ✅ Use enum
fun setTheme(theme: AppTheme) {
    when (theme) {
        AppTheme.DARK   -> applyDark()
        AppTheme.LIGHT  -> applyLight()
        AppTheme.SYSTEM -> applySystem()
    }
}

Mistake 2: Forgetting the semicolon before functions

// ❌ Compile error — missing semicolon before abstract function
enum class Shape {
    CIRCLE,
    SQUARE

    abstract fun area(): Double  // Error — need ; before functions
}

// ✅ Semicolon after last constant
enum class Shape {
    CIRCLE,
    SQUARE;   // semicolon here!

    abstract fun area(): Double
}

Mistake 3: Using ordinal for logic

// ❌ Fragile — if you reorder enum values, ordinal changes
if (status.ordinal < OrderStatus.SHIPPED.ordinal) {
    showTrackButton()
}

// ✅ Explicit comparison
val shippableStatuses = listOf(OrderStatus.PENDING, OrderStatus.PROCESSING)
if (status in shippableStatuses) {
    showTrackButton()
}

Mistake 4: Using sealed class when enum is enough

// ❌ Overkill
sealed class LogLevel {
    object Debug : LogLevel()
    object Info : LogLevel()
    object Warning : LogLevel()
    object Error : LogLevel()
}

// ✅ Enum is perfect — all cases are identical shape
enum class LogLevel { DEBUG, INFO, WARNING, ERROR }

Quick Reference

Feature Code
Declare enum enum class Name { A, B, C }
Access constant Name.A
Get name as String Name.A.name
Get position Name.A.ordinal
All constants Name.entries
From String Name.valueOf("A")
Enum with properties enum class Name(val x: Int) { A(1), B(2) }
Enum with function Add function after ;
Abstract per-constant abstract fun f() + override in each
Implement interface enum class Name : Interface { ... }

Summary

  • Enum classes represent a fixed set of named constants with full type safety
  • Enums integrate perfectly with when — compiler checks exhaustiveness
  • Enum constants can have properties — pass values in the constructor
  • Enum classes can have shared functions and abstract functions per constant
  • Use entries (Kotlin 1.9+) or values() to iterate all constants
  • Use valueOf() to get an enum from a String — or entries.find { } for safe lookup
  • Use enums when all cases have the same shape — use sealed classes when each case needs different data
  • Don't use ordinal for business logic — add explicit properties or functions instead
  • Remember the semicolon after the last constant when adding functions or properties

Enums are one of those features that seem simple at first but have a lot of depth. Used correctly, they make your code dramatically safer and more expressive.

Happy coding!