C.W.K.
Stream
Lesson 03 of 05 · published

Equality Narrowing: `===` and `==`

~8 min · narrowing, equality, discriminated-unions

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"x === 'literal' is how the compiler verifies your discriminated unions are wired correctly."

Equality as a narrowing operation

Comparing a value to a literal narrows it. if (x === 'red') inside the branch makes the compiler know x is the literal type 'red'. If x was previously 'red' | 'blue' | 'green', it's now just 'red'. In the else branch, it's 'blue' | 'green'.

This is the operator that makes discriminated unions work in switch and chained if statements. if (shape.kind === 'circle') narrows the whole shape object to the circle variant.

Loose equality (==) — almost never use it

JavaScript's == performs type coercion: '1' == 1 is true. TypeScript treats == as a narrowing operator, but the narrowing follows the coercion rules — which are confusing and almost never what you want.

The rule: always use === for narrowing. Reserve == only for explicit null/undefined checks (x == null matches both null AND undefined — a deliberate idiom that's clear in context). Otherwise, strict equality.

Narrowing on === with constants from as const

Combining as const with equality narrowing is a very common pattern. const STATUSES = ['idle', 'loading', 'done'] as const gives you literal types. Then any x === STATUSES[0] correctly narrows x to 'idle'.

Strict equality is the workhorse of narrowing for tagged unions. Stay strict; lean on literal types; trust the compiler's inference.

Code

Strict equality and the one legitimate `==` use·typescript
type Status = 'idle' | 'loading' | 'done' | 'error';

function handle(s: Status): string {
  if (s === 'idle') {
    return 'waiting';           // s: 'idle'
  }
  if (s === 'loading') {
    return 'spinner';           // s: 'loading'
  }
  // s narrowed to 'done' | 'error' here
  if (s === 'done') {
    return 'check';             // s: 'done'
  }
  return 'cross';               // s: 'error' — by elimination
}

// == null catches BOTH null and undefined.
function defaultName(name: string | null | undefined): string {
  if (name == null) {           // matches null OR undefined
    return 'anonymous';
  }
  return name;                  // name: string
}

External links

Exercise

Write a function priority(status: Status): number where Status is a literal union of 4 values. Use chained equality narrowing to assign a different number to each. Don't use a switch — just if chains. Confirm the compiler narrows correctly through the chain.
Hint
After each if (s === '...') return, the compiler removes that variant from s's type. By the final branch, only one variant remains — the compiler knows which.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.