Duplicate Branches

ID

kotlin.duplicate_branches

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

Kotlin

Tags

code-style, duplicate-code

Description

Reports a conditional structure whose branches all run exactly the same code: an if / else pair or an if / else if / else chain where every body is identical, or a when where every branch is identical. Both when forms are covered — with a subject and without.

Rationale

fun bumpIfVerbose(verbose: Boolean, count: Int): Int {
    if (verbose) {
        log(count)
        return count + 1
    } else {
        log(count)
        return count + 1
    }
}

When every branch does the same thing, the condition decides nothing. Either the whole structure is dead weight that should collapse to the body alone, or — far more often — one branch was copy-pasted and the edit that was supposed to follow never happened, so one of the cases is silently mishandled. The second reading is why this is reported as a defect rather than as tidiness: the code compiles, the tests for the copied branch pass, and the branch that was never edited is the one that goes wrong in production.

The trigger is deliberately all branches, not any two of them. Two arms of a long dispatch chain sharing a body is a normal, intentional idiom; every arm sharing a body never is, at any body size.

Notes

The check stays narrow so that idiomatic code never fires:

  • The chain must be complete. An if / else if with no trailing else is not a candidate — falling through the whole chain does something different from entering any branch, so the conditions still decide something.

  • The when must carry an else, for the same reason. This is a deliberate trade with a known cost: an exhaustive when over an enum or a sealed hierarchy needs no else, and such a when is not reported even when every branch is identical. An enum-entry branch (Speed.FAST →) is an ordinary expression in Kotlin, indistinguishable at parse time from any other constant, so the rule has no way to establish exhaustiveness without the else. Fewer false positives is worth more here than the extra recall.

  • Empty bodies are left alone. An empty branch may be a deliberate no-op, and it is already reported by kotlin.empty_when_branch or kotlin.empty_if_body.

  • Branches that type-test the subject are excluded. Under is Int → the subject is smart-cast to a different static type, so two textually identical bodies do not run the same code — value.toString() under is Int and under is Long call different members.

  • Branches carrying a guard are excluded. A guard (Speed.FAST if level > 0 →) adds a condition the bodies say nothing about.

Bodies are compared as statement lists, so the comparison is brace-agnostic: if (v) log(c) else { log(c) } is caught even though one side has braces. Equivalence is computed over the parsed-token sequence, so comments and formatting differences between two branches do not hide a match.

Remediation

Decide which reading applies, then act on it.

If the branches were meant to differ, fix the branch that was never edited:

fun bumpIfVerbose(verbose: Boolean, count: Int): Int {
    if (verbose) {
        log(count)
        return count + 1
    } else {
        return count + 1
    }
}

If they were always meant to be the same, drop the condition and keep the body:

fun bumpIfVerbose(verbose: Boolean, count: Int): Int {
    if (verbose) {
        log(count)
    }
    return count + 1
}

For a when whose branches genuinely share one body, merge the conditions instead of repeating them:

fun rateFor(speed: Speed): Int =
    when (speed) {
        Speed.FAST, Speed.SLOW -> 10
    }

© 2026 Xygeni. All rights reserved