Nested When

ID

kotlin.no_nested_when

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

Kotlin

Tags

code-style, conditional, control-flow

Description

Reports a when written inside a branch of another when. The inner when is the one reported, because that is the one to extract.

Both when forms are covered: the one with a subject (when (x) { …​ }) and the subject-less one (when { cond → …​ }), which is an if / else if ladder in when clothing. A when used as an expression counts the same as one used as a statement — a val label = when { …​ } inside a branch of an outer when is still a second decision table in the same place.

Rationale

fun dispatch(kind: Int, tier: Tier): String =
    when (kind) {
        1 ->
            when (tier) {                   // two tables to hold in mind at once
                Tier.GOLD -> "1-gold"
                Tier.SILVER -> "1-silver"
            }

        else -> "other"
    }

Each when is already a table of conditions the reader has to hold in mind at once. Nesting one inside a branch multiplies the paths through the function and hides the inner table behind a branch arrow, where nobody looks for it — which is why the combination is a frequent source of branches that were never handled.

In a three-deep stack the middle and innermost when are each reported against their own nearest enclosing one: each is independently a level too many.

Notes

Nesting is lexical but scoped to one unit of code:

  • An inner when wrapped in an if or a for inside the branch still counts — the reader still meets it while reading the outer branch.

  • A when inside a lambda passed to let, run, map or forEach, inside a local or anonymous function, inside a property accessor, or inside an object expression is not reported. That body is a separate unit the reader already considers on its own, and moving the table there is exactly the fix this rule asks for.

Branching depth for if is measured separately, by kotlin.nested_if.

Remediation

Give the second dimension a name: lift the inner when into its own function, so each table can be read on its own.

fun dispatch(kind: Int, tier: Tier): String =
    when (kind) {
        1 -> forTier(tier)
        else -> "other"
    }

private fun forTier(tier: Tier): String =
    when (tier) {
        Tier.GOLD -> "1-gold"
        Tier.SILVER -> "1-silver"
    }

When the two subjects are genuinely independent, a single when over a pair is often clearer still:

fun dispatch(kind: Int, tier: Tier): String =
    when (kind to tier) {
        1 to Tier.GOLD -> "1-gold"
        1 to Tier.SILVER -> "1-silver"
        else -> "other"
    }

© 2026 Xygeni. All rights reserved