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

`typeof` Narrowing

~8 min · narrowing, typeof, primitives

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"The most basic narrowing tool. Works on every primitive, lights up everywhere."

The value-level typeof

Recall from the Foundations track: typeof has two meanings. The value-level typeof runs at runtime and returns one of the seven strings: 'string', 'number', 'boolean', 'undefined', 'object', 'function', 'symbol', or 'bigint'. (Eight strings if you count null — but typeof null === 'object', a famous JavaScript quirk.)

TypeScript understands this exact set of strings as a narrowing operation. if (typeof x === 'string') inside that branch narrows x to string. Outside the branch, the type is whatever's left of the union minus string.

What typeof can narrow

The seven primitive types: string, number, boolean, undefined, object, function, symbol, bigint. That's the full set. Anything more specific (a particular object shape, a class instance, a literal) needs a different narrowing tool — typeof can only see as far as the primitive layer.

The typeof === 'object' trap

typeof null === 'object' returns true. This is a JavaScript bug from 1995 that became a feature. If you narrow on typeof x === 'object' and your union includes null, null is still in scope inside the branch.

The fix: add an explicit null check. if (typeof x === 'object' && x !== null). TypeScript even has this built into its narrowing — it knows that after the null check, x is the non-null object types only.

typeof is for primitives. For objects, classes, and shape checks, you'll use instanceof, in, or type predicates. Reach for the right tool for the type you're checking.

Code

typeof — the right cases and the null trap·typescript
// typeof narrowing for primitives.
function toUpper(x: string | number): string {
  if (typeof x === 'string') {
    return x.toUpperCase();        // x: string
  }
  return x.toFixed(2);             // x: number
}

// The null trap.
function unsafe(x: { name: string } | null): string {
  if (typeof x === 'object') {
    // x: { name: string } | null  — null is still here!
    return x.name;                 // ❌ Object is possibly 'null'
  }
  return '';
}

function safe(x: { name: string } | null): string {
  if (typeof x === 'object' && x !== null) {
    return x.name;                 // ✅ narrowed to { name: string }
  }
  return '';
}

External links

Exercise

Write a function describe(x: string | number | null) that returns 'a string', 'a number', or 'nothing'. Use typeof and explicit null checks. Make sure the compiler doesn't complain about any branch — and hover each x to confirm the type is what you expect at every line.
Hint
The null check has to come either first or with typeof === 'object' && x !== null. typeof === 'string' and typeof === 'number' each narrow the obvious way. The order matters less than the explicitness.

Progress

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

Comments 0

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

No comments yet — be the first.