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)

Self-arithmetic always produces a constant: 'x - x' is always 0, 'x / x' is always 1, and 'x mod x' and 'x mod 1' are always 0 (likewise 'rem'). Arithmetic between two literals such as '3 + 2' is not flagged, and neither are 'x + x' or 'x * x'.

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

#!ek9
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 '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: E08082, E08083, E08084, E08085, E08086, E08087, E08094, always, clean-code, coalescing, comparison, condition, constant, false, metric, migrate, quality, redundant, tautology, true