Duplicate Condition In Branch Chain

ID

kotlin.duplicate_condition_in_if_chain

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Kotlin

Tags

control-flow, dead-code, reliability

Description

Reports a condition that is structurally identical to an earlier condition in the same if / else if chain, or in the same when. Both run top to bottom and stop at the first branch that matches, so the later, duplicated branch can never be reached.

Rationale

fun label(a: Int): String {
    if (a == 1) {
        return "one"
    } else if (a == 2) {
        return "two"
    } else if (a == 1) {          // unreachable
        return "also one"
    }

    return "other"
}

fun rate(a: Int): Int =
    when (a) {
        1 -> 10
        2 -> 20
        1 -> 30                   // unreachable
        else -> 0
    }

Anything matching the repeated condition triggers the earlier branch, so the code in the later one never runs. Almost always the condition was copy-pasted and one operand was left unchanged: the case the author meant to add is missing, and the branch that took its place is dead. Both halves of that mistake are silent — the code compiles and every existing test still passes.

Notes

  • Comparison is scoped to a single chain or a single when. The same condition written in two unrelated if statements, or in an inner chain nested inside an outer one, is ordinary code and is not reported.

  • Comparison is structural, over the parsed-token sequence, so it does not confuse operand order: a == b and b == a are not flagged even though they are logically equivalent, and neither are a && b and b && a. Formatting and comments make no difference.

  • In a when, conditions are compared one by one rather than branch by branch, so in a comma list the individual repeated value is reported. With 1, 2 → followed by 2 →, the second branch is not entirely dead, but its condition is. Every condition form is compared the same way — a constant, an is type test, an in range test, and the bare boolean expressions of a subject-less when.

  • A when branch carrying a guard (1 if level > 0 →) is skipped in either position: the guard adds a condition of its own, so the same value may legitimately appear again on a branch that applies when the guard does not hold.

  • A condition that contains a call is excluded in either position. A call can legitimately return a different value on each evaluation — a counter, an iterator, a mutable property — so two textually identical calls do not make the later branch provably dead.

Remediation

Write the condition that was meant to be there:

fun label(a: Int): String {
    if (a == 1) {
        return "one"
    } else if (a == 2) {
        return "two"
    } else if (a == 3) {
        return "three"
    }

    return "other"
}

When the two branches really do share one outcome, merge the conditions instead of repeating them:

fun rate(a: Int): Int =
    when (a) {
        1, 2 -> 10
        else -> 0
    }

© 2026 Xygeni. All rights reserved