What are tautological conditions and how do I fix them?

← Code Quality · Ref: Q558

A tautological condition is one whose outcome is already determined at compile time. EK9 detects these as dead code indicators.

CONSTANT COMPARISON (E08082)

Comparing two literals produces a known result: '3 > 2' is always true. Use a named constant or computed value instead.

CONSTANT ARITHMETIC (E08083)

Arithmetic on two literals produces a constant: '3 + 2' should be expressed as the constant 5. Use a named constant.

REDUNDANT BOOLEAN COMPARISON (E08084)

Comparing a Boolean expression with a literal is redundant: 'if flag == true' should be 'if flag'. The comparison adds nothing.

LOGICAL TAUTOLOGY (E08085)

Logical expressions that always produce the same result: 'x or not x' is always true. 'x and not x' is always false.

FLOW-SENSITIVE CONDITIONS (E08086/E08087)

When the compiler tracks variable values through control flow, it can prove some conditions are predetermined. After 'if x > 10', checking 'x > 5' inside that branch is always true (E08086). In the else branch of 'if x == 5', checking 'x == 5' is always false (E08087).

CONSTANT COALESCING (E08094)

Coalescing operators between two literals produce a known result: '5 <? 3' is always 3. The coalescing operators (<?, >?, <=?, >=?) return a value, not a Boolean, but when both operands are literals the result is still predetermined. Use a variable or named constant instead.

COMMON PATTERNS THAT TRIGGER

Redundant else re-check: 'if x == 5 ... else if x == 5' — the else already implies x is not 5. Post-throw re-check: after 'if x < 0 throw', checking 'x < 0' again is always false. Implied range: 'if x > 100 ... if x > 10' — the inner check is subsumed by the outer.

HOW TO FIX

Use named constants instead of raw literals. Remove redundant inner conditions. Use else-if chains where each branch tests a genuinely different range. Trust post-throw narrowing instead of re-checking.

See Q557 for flow-sensitive dead code detection. See Q311 for the full quality checks catalog. See Q318 for self-comparison detection. See Q637 for safe variable comparison patterns. See Q638 for named constant patterns. See Q639 for Boolean usage without literals. See Q640 for collection isSet check patterns.

Example

defines module qa.codequality.tautology

  defines constant

    MINIMUM_AGE <- 18

    MAXIMUM_AGE <- 120

    SPEED_LIMIT <- 65

    PENALTY_THRESHOLD <- 80

    DANGER_THRESHOLD <- 100

  defines function

    computeAge()
      <- rtn as Integer: 25

    <?-
      Correct: each branch tests a genuinely different range.
      The compiler would reject 'if age >= 18; if age >= 10' because
      the inner check would be always true when the outer is true.
    -?>
    classifyAge() as pure
      -> age as Integer
      <- category as String: "minor"

      if age >= MAXIMUM_AGE
        category: "centenarian"
      else if age >= MINIMUM_AGE
        category: "adult"

    <?-
      Correct: using named constants for comparisons avoids E08082.
      Raw literals like 'if speed > 65' would trigger magic literal detection.
    -?>
    classifySpeed() as pure
      -> speed as Integer
      <- category as String: "safe"

      if speed >= DANGER_THRESHOLD
        category: "dangerous"
      else if speed >= PENALTY_THRESHOLD
        category: "ticket-worthy"
      else if speed > SPEED_LIMIT
        category: "over-limit"

    <?-
      Correct: uses a Boolean directly instead of comparing with true/false.
      Writing 'if isEnabled == true' would trigger E08084.
    -?>
    formatFlag() as pure
      -> isEnabled as Boolean
      <- label as String: "disabled"

      if isEnabled
        label: "enabled"

    <?-
      Correct: after throwing on invalid input, the remaining code
      can trust the variable satisfies the inverse condition without re-checking.
    -?>
    validateAndProcess()
      -> score as Integer
      <- result as String: "processed"

      if score < 0
        throw Exception("score must not be negative")

      adjustedScore <- computeAge()
      if adjustedScore < 0
        result: "adjusted-score-negative"

  defines program

    TautologicalConditionsDemo()
      stdout <- Stdout()

      personAge <- 42
      ageCategory <- classifyAge(personAge)
      stdout.println(`Age ${personAge}: ${ageCategory}`)

      carSpeed <- 72
      speedCategory <- classifySpeed(carSpeed)
      stdout.println(`Speed ${carSpeed}: ${speedCategory}`)

      featureOn <- true
      flagLabel <- formatFlag(featureOn)
      stdout.println(`Feature: ${flagLabel}`)

      testScore <- 85
      outcome <- validateAndProcess(testScore)
      stdout.println(`Score ${testScore}: ${outcome}`)

Common mistakes

E08084 — Comparing a Boolean to the literal true is redundant. The Boolean IS the condition. Use it directly. See ek9 -h E08084 for details.

Incorrect:

if isEnabled == true

Correct:

if isEnabled

E08082 — Comparing two literal values produces a compile-time constant. Use named constants compared against variables. See ek9 -h E08082 for details.

Incorrect:

if 120 >= 18

Correct:

if age >= MAXIMUM_AGE
Other ways to ask this
  • Why does the compiler say my condition is always true?
  • What is E08082 constant comparison?
  • How do I fix a redundant condition in an if statement?
  • Why is my boolean comparison flagged as tautological?

Coming from another language?

Java: no tautological condition detection in javac. SpotBugs has limited checks. SonarQube detects some constant conditions. Rust: clippy has logic_bug and redundant_pattern lints. Go: no tautology detection. Python: pylint detects some constant conditions. C++: -Wtautological-compare warns on a few patterns. EK9: comprehensive detection covering constant comparison, constant coalescing, boolean redundancy, logical tautology, and flow-sensitive narrowing, all as mandatory compiler errors.

Keywords: clean-code, coalescing, tautology, comparison, metric, E08087, condition, false, E08086, E08085, true, E08084, E08083, E08082, constant, always, E08094, redundant, migrate, quality