Else-If Without Else

ID

kotlin.else_if_without_else

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Code Smell

Language

Kotlin

Tags

code-style, conditional, control-flow

Description

Reports an if / else if chain that does not end with a plain else. Only chains with at least one else if are reported: a lone if, and a plain if / else with no else if, are out of scope.

The rule is disabled by default. It encodes a house-style judgement — that every enumerated chain states its remaining case explicitly — which is too noisy to impose on every project. Enable it per project or per policy.

Rationale

fun count(level: String, counters: Counters) {
    if (level == "warn") {
        counters.warn++
    } else if (level == "error") {
        counters.error++
    }
}

Once a condition is spelled out more than once, the chain reads as a decision table, and the reader takes the absence of a final else to mean the listed conditions cover every input. When they do not, control leaves the chain with whatever state it already had, and neither the reader nor the next person to add a condition can tell whether doing nothing was the intended behaviour for that input or whether the branch was simply forgotten.

Notes

  • A final else block that is empty counts as a missing one unless it holds a comment. An else with real statements, or with only a comment saying why nothing is done, both close the chain. That second reading is the cheap fix when the fall-through really is intended.

  • Kotlin’s if is also an expression, and an expression must be exhaustive to compile — so the compiler already requires the final else there, and val s = if (a) 1 else if (b) 2 does not compile at all. This rule can therefore only ever fire on the statement form; on the expression form there is nothing left for it to find.

  • A chain whose every branch returns is still reported. Kotlin has no idiom that asks for the final case to be written unindented after the chain, so a returning chain with no else carries the same unstated case as any other.

Remediation

State the remaining case:

fun count(level: String, counters: Counters) {
    if (level == "warn") {
        counters.warn++
    } else if (level == "error") {
        counters.error++
    } else {
        counters.other++
    }
}

When the fall-through is genuinely intended, say so in the else:

fun count(level: String, counters: Counters) {
    if (level == "warn") {
        counters.warn++
    } else if (level == "error") {
        counters.error++
    } else {
        // every other level is intentionally not counted
    }
}

For a chain that enumerates values of one subject, a when says the same thing and makes the final case a syntactic requirement when it is used as an expression:

fun count(level: String, counters: Counters) {
    when (level) {
        "warn" -> counters.warn++
        "error" -> counters.error++
        else -> counters.other++
    }
}

© 2026 Xygeni. All rights reserved