← Debugging Guides

阅读中文版本

TypeScript TS2367 — Comparison Appears Unintentional

Fix TS2367 "This comparison appears to be unintentional" by aligning literal and union types and avoiding redundant checks that the type already rules out.

intermediatedebuggingtypescripttypesnarrowing

Symptoms

  • error TS2367: This comparison appears to be unintentional because the types have no overlap
  • A status === check fails after narrowing a union
  • The code compiled before a type was tightened

Possible causes

  • A comparison uses a literal the union type cannot hold
  • An earlier narrowing already excluded the compared value
  • The variable type is narrower than the runtime value, suggesting a type bug

Diagnosis steps

Read which types are compared

npx tsc --noEmit 2>&1 | grep TS2367

Interpretation

The message shows both compared types, revealing the impossible branch.

Reconcile the type with reality

grep -n "type Status\|: '\w*'" src/status.ts

Interpretation

Comparing the declared union with actual values decides whether to widen the type or remove the check.

Root cause

TypeScript reports TS2367 when the compared operands cannot overlap given their static types, because the branch can never be true. It often signals a stale narrowing or a type that no longer matches the runtime data.

Fix

Remove the impossible check, widen the type to include the runtime value, or fix the type that was incorrectly narrowed; do not cast to silence it.

Risk note

Casting to any to bypass TS2367 hides a genuine mismatch that will misbehave at runtime.

Verify the fix

Run tsc --noEmit and confirm TS2367 is gone; add a test that exercises the previously impossible value and asserts the intended behavior.

Symptoms

When you hit “TypeScript TS2367 — Comparison Appears Unintentional”, guessing wastes time. The guide below follows evidence: reproduce the symptom, run targeted diagnostics, identify the root cause, apply the fix, and verify it with concrete checks.

Observed symptoms include:

  • error TS2367: This comparison appears to be unintentional because the types have no overlap
  • A status === check fails after narrowing a union
  • The code compiled before a type was tightened

Reproduce and Diagnose

Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.

Step 1: Read which types are compared

npx tsc --noEmit 2>&1 | grep TS2367

The message shows both compared types, revealing the impossible branch.

Step 2: Reconcile the type with reality

grep -n "type Status\|: '\w*'" src/status.ts

Comparing the declared union with actual values decides whether to widen the type or remove the check.

Likely contributors, in order of frequency:

  • A comparison uses a literal the union type cannot hold
  • An earlier narrowing already excluded the compared value
  • The variable type is narrower than the runtime value, suggesting a type bug

Root Cause

TypeScript reports TS2367 when the compared operands cannot overlap given their static types, because the branch can never be true. It often signals a stale narrowing or a type that no longer matches the runtime data.

The Fix

Remove the impossible check, widen the type to include the runtime value, or fix the type that was incorrectly narrowed; do not cast to silence it.

Watch out: Casting to any to bypass TS2367 hides a genuine mismatch that will misbehave at runtime.

type Status = 'idle' | 'running' | 'done';

function label(s: Status) {
  if (s === 'running') return 'busy';
  if (s === 'done') return 'finished';
  // ❌ if (s === 'cancelled') return 'x'; // TS2367: no overlap with Status
  return 'idle';
}

// fix the union when the runtime really can be 'cancelled'
type Status2 = Status | 'cancelled';

Verify and Prevent

Run tsc –noEmit and confirm TS2367 is gone; add a test that exercises the previously impossible value and asserts the intended behavior.

Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.

This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.