Skip to content

L3: Hierarchies and Exhaustive Control Flow

The Hell of Java’s switch (The Fall-Through Bug)

Section titled “The Hell of Java’s switch (The Fall-Through Bug)”

If you come from Java, C, or JavaScript, you are undoubtedly familiar with the switch statement. Historically, switch has been one of the most error-prone control structures in software engineering. Why? Because of Fall-Through.

In those languages, if you forget to write the break; keyword at the end of a case, execution silently cascades into the next case below, causing insidious, hard-to-trace logic bugs.

Kotlin decided to eliminate this problem at its root by abandoning the switch keyword entirely and introducing a far more powerful, expressive, and safe construct: when.


In Kotlin, structures like if and when are not mere statements (imperative instructions); they are Expressions. This means that they return a value.

In Java, if you wanted to assign a value conditionally, you had to declare a mutable variable and modify it inside each branch of the if or switch. In Kotlin, you can assign the evaluation result of a when directly to a read-only identifier (val).

// We assign the evaluation result of the entire 'when' directly to the variable.
// There is no need for intermediate mutable 'var' variables.
val selectedOption: MenuOption = when (rawOption) {
"1" -> MenuOption.CHAT
"2" -> MenuOption.SETTINGS
"3" -> MenuOption.EXIT
else -> MenuOption.UNKNOWN
}

How does when work inside the Java Virtual Machine (JVM)? The Kotlin compiler intelligently optimizes its bytecode output depending on how you use when:

  1. Few Branches: If your when only checks 2 or 3 conditions, the compiler emits a straightforward chain of if-else if instructions.
  2. Multiple Branches or Enums: When evaluating many discrete cases or an enum class, the compiler activates optimized JVM Bytecode instructions:
    • TABLESWITCH: Used when matching contiguous integers. Offers constant-time access ($O(1)$) via direct indexed jump tables.
    • LOOKUPSWITCH: Used when keys are sparse or non-contiguous. Executes binary search across sorted keys, guaranteeing logarithmic access time ($O(\log n)$).

🧠 Conceptual Bridge: Control Flow and Pattern Matching in Your Language

Section titled “🧠 Conceptual Bridge: Control Flow and Pattern Matching in Your Language”
  • In classic JS/TS, the switch statement suffers from dangerous fall-through if you omit break;, and it is not an expression (cannot yield a value directly to a const).
  • To simulate pattern matching in TypeScript, developers rely on Discriminated Unions (type Action = { type: 'chat' } | { type: 'exit' }) evaluated inside a switch(action.type) or external libraries like ts-pattern.
  • In Kotlin, when supports first-class expressions, discards break by design, and performs automatic Smart Casts within each branch without third-party dependencies.

Before jumping into the lab, we need one more tool: the while loop.

In Kotlin, the traditional for loop is reserved almost exclusively for iterating over collections or ranges (which we explore in Lesson 7). When we need to keep an interactive application running continuously (such as the Game Loop of a game or our CLI menu), we use a while loop.

// Keeps the application running in an infinite loop
// until we explicitly execute the 'break' keyword.
while (true) {
// 1. Display menu
// 2. Read input
// 3. Evaluate input with 'when'
}

The Superpower: Exhaustive Control Flow (Compiler Certainty)

Section titled “The Superpower: Exhaustive Control Flow (Compiler Certainty)”

The true brilliance of Kotlin is not just that when is elegant, but its guarantee of Exhaustive Control Flow.

When you use when as an expression (yielding a value) or when evaluating a “closed hierarchy”, the compiler mathematically forces you to handle EVERY possible case. If you omit even one branch, your code will fail to compile.

To unlock this superpower, Kotlin provides Closed Hierarchies:

Used to represent a fixed, constant set of discrete values. Think of them as an absolute switch of predefined states (e.g., Days of the week, Order status, Menu options).

// We define a strict set of valid options for our application.
enum class MenuOption {
CHAT,
SETTINGS,
EXIT,
UNKNOWN
}

If we pass a MenuOption to a when, Kotlin requires a branch for CHAT, SETTINGS, EXIT, and UNKNOWN. If you later add HELP to the enum, the compiler will immediately highlight every when statement across your codebase where HELP is unhandled! This makes refactoring exceptionally safe.

2. Sealed Classes (sealed class and sealed interface)

Section titled “2. Sealed Classes (sealed class and sealed interface)”

Enums on steroids. While an enum only holds constant values (like static buttons), a sealed class allows each option to maintain its own distinct state and payload data. For example, a network result can be modeled as Error(val exception: Exception) or Success(val data: String). We explore these in depth in Lesson 10: Companion Objects and Object Declarations.


Today we take our AI Chat CLI to the next level. We will wrap the greeting from Lesson 2 inside an infinite loop and present an interactive menu. We will parse raw text input into our strongly-typed enum class to guarantee exhaustive control flow.

At the top of your Main.kt file (outside the main function), declare an enum to mathematically restrict menu options:

// Define an Enum to represent our menu options strictly.
enum class MenuOption {
CHAT,
SETTINGS,
EXIT,
UNKNOWN
}

Directly below where we greet the user (Hello, $username!), open a while (true) loop and print the menu options:

// Main loop that keeps the interactive CLI alive
while (true) {
println("\n--- Main Menu ---")
println("1. Chat")
println("2. Settings")
println("3. Exit")
print("Choose an option: ")
val rawOption: String? = readlnOrNull()
// We will continue Step 3 inside here...
}

Step 3: Safe Parsing Using when as an Expression

Section titled “Step 3: Safe Parsing Using when as an Expression”

Inside the while loop, directly below readlnOrNull(), use when to convert the raw string "1" into our type-safe constant MenuOption.CHAT. Notice how we assign the result directly to val selectedOption:

// Use 'when' as an expression to map raw string input
// to our structured Enum, returning the value directly.
val selectedOption: MenuOption = when (rawOption) {
"1" -> MenuOption.CHAT
"2" -> MenuOption.SETTINGS
"3" -> MenuOption.EXIT
else -> MenuOption.UNKNOWN
}

Now use our structured Enum to execute application logic. Write this second when directly below the previous one:

// Use 'when' with our Enum. Because it is a closed hierarchy
// (the compiler knows all possible cases), it enforces exhaustive
// branch handling without requiring a default 'else'.
when (selectedOption) {
MenuOption.CHAT -> {
println(">>> Entering chat mode... (Work in progress)")
}
MenuOption.SETTINGS -> {
println(">>> Entering settings... (Work in progress)")
}
MenuOption.EXIT -> {
println("Goodbye, $username! See you soon.")
break // Breaks the while(true) loop and exits cleanly
}
MenuOption.UNKNOWN -> {
println("Invalid option. Please try again.")
}
}

Launch your interactive application using your preferred workflow:

  • Inside Android Studio / IntelliJ IDEA: Click the green Play (▶️) button next to fun main() and type into the bottom Run console.
  • Inside Google Antigravity / Visual Studio Code: Press Cmd + Shift + B (macOS) or Ctrl + Shift + B (Windows/Linux) to launch the integrated interactive terminal.
  • In Command-Line Terminal: Run:
    Ventana de terminal
    ./gradlew run --console=plain -q
    (On Windows: gradlew.bat run --console=plain -q)

Test Cases to Verify:

  1. After entering your name, the main menu displays with all 3 options.
  2. Type 1 and press Enter: it prints the chat mode notice and redisplays the menu (thanks to the while loop).
  3. Type an invalid input (such as "9" or "hello"): the exhaustive MenuOption.UNKNOWN branch catches it and displays "Invalid option. Please try again.".
  4. Type 3: the break command terminates the while (true) loop and prints a personalized farewell.

Congratulations! You have built the interactive backbone of our CLI application using exhaustive control flow.



Have questions about exhaustive when, JVM bytecode jump tables, or the menu loop?