TypeScript error

TS2367: A comparison that can never be true

  • Error TS2367
  • Tested with TypeScript 5.9

error TS2367: This comparison appears to be unintentional because the types '"pending" | "shipped" | "cancelled"' and '"canceled"' have no overlap. is reported for ===, !==, == and != when no value could belong to both types. The comparison is then always false (or always true for !==), which is almost never what the code means.

With string literal unions from JSDoc, this check catches misspelled states the moment you write them. It also catches loose JavaScript habits such as comparing a number with the string "0".

At a glance

CodeTS2367
MessageThis comparison appears to be unintentional because the types '{0}' and '{1}' have no overlap.
TypeScript's categoryerror
Listed underTypes do not match
Turned on by No option: every checked file reports it
In .js files Reported when the file is checked: checkJs in jsconfig.json, or // @ts-check at the top of the file

The message is TypeScript's own text from its diagnostic messages file (Apache-2.0); {0} and {1} are filled in by tsc for each error.

What it looks like

order.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: order.js(6,10): error TS2367: This comparison appears to be unintentional because the types '"pending" | "shipped" | "cancelled"' and '"canceled"' have no overlap. order.js(12,10): error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. order.js(6,10): error TS2367: This comparison appears to be unintentional because the types '"pending" | "shipped" | "cancelled"' and '"canceled"' have no overlap. order.js(12,10): error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. order.js(6,10): error TS2367: This comparison appears to be unintentional because the types '"cancelled" | "pending" | "shipped"' and '"canceled"' have no overlap. order.js(12,10): error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap. checked 21 October 2026
/** @typedef {{ id: string, status: "pending" | "shipped" | "cancelled", items: number }} Order */

/** @param {Order} order */
export function canReorder(order) {
  return order.status === "canceled";error TS2367: This comparison appears to be unintentional because the types '"pending" | "shipped" | "cancelled"' and '"canceled"' have no overlap.
}

/** @param {Order} order */
export function isEmpty(order) {
  return order.items === "0";error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap.
}
A misspelled status, and a number compared with a string.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

What causes it

  • A string literal is misspelled or spelled differently from the union ("canceled" against "cancelled").
  • A number is compared with a string (or a string with a number) because the value was read as text somewhere else.
  • The variable was narrowed earlier in the function, so the remaining type no longer contains the value compared against.

How to fix it

Compare against a value the type can hold

Fix the literal, or convert one side so both are numbers or both are strings. In the editor, completions inside the quotes of order.status === "" list the union's members.

order.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/** @typedef {{ id: string, status: "pending" | "shipped" | "cancelled", items: number }} Order */

/** @param {Order} order */
export function canReorder(order) {
  return order.status === "cancelled";
}

/** @param {Order} order */
export function isEmpty(order) {
  return order.items === 0;
}
Comparisons that can be true.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Or add the value to the union

If the value really can occur (an API added a new status), add it to the @typedef union. Every other comparison and switch over the union is then checked against the new member too.

Gotchas

  • After an early return for one case, comparing with that same case later is reported, because the type has been narrowed. In that case the error points at dead code.
  • == gets the same check; it does not let a number and a numeric string through, even though JavaScript coerces them.

Sources

Last reviewed 20 October 2026.