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
| Code | TS2367 |
|---|---|
| Message | This comparison appears to be unintentional because the types '{0}' and '{1}' have no overlap. |
| TypeScript's category | error |
| Listed under | Types 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
/** @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.
}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.
/** @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;
}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
returnfor 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
- Everyday Types: Literal Types · TypeScript handbook
- Narrowing: Equality narrowing · TypeScript handbook
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 20 October 2026.