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.
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 TS2367Interpretation
The message shows both compared types, revealing the impossible branch.
Reconcile the type with reality
grep -n "type Status\|: '\w*'" src/status.tsInterpretation
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.