Flow-Sensitive Typing — TS Tracks What's True
Narrowing
TypeScript narrows a value's type based on the code path. Inside a `typeof` check, the value is known to be that type.
What you'll learn
- Recognize narrowing in action
- Use truthiness, equality, `typeof`, `in`
- Understand control-flow analysis
Narrowing is TypeScript’s flow-sensitive type analysis. As your code branches, TS tracks what each branch knows about each value. The type at any point depends on what’s been checked so far.
The Basic Example
function shout(x: string | number) {
// here, x is string | number
if (typeof x === "string") {
// here, x is string
return x.toUpperCase();
}
// here, x is number
return x.toFixed(2);
} TS sees the typeof check and narrows x accordingly in each
branch.
Truthiness Narrowing
function greet(name: string | null) {
if (name) {
// name is string (null was falsy)
return `Hi, ${name.toUpperCase()}`;
}
return "Hi, stranger";
} if (name) rules out null, undefined, 0, "", false,
NaN. After the check, name is string.
Equality Narrowing
function area(shape: "circle" | "square", size: number) {
if (shape === "circle") {
// shape is "circle"
return Math.PI * size ** 2;
}
// shape is "square"
return size * size;
} Equality with a literal narrows.
typeof Narrowing
typeof works for JS primitives:
| Value type | typeof x returns |
|---|---|
string | "string" |
number | "number" |
boolean | "boolean" |
bigint | "bigint" |
symbol | "symbol" |
undefined | "undefined" |
function | "function" |
object / null / arrays | "object" |
typeof is fast and built-in. Use it whenever the union members
are primitives.
Early Returns
A common pattern — narrow by returning early:
function process(user: User | null) {
if (!user) return;
// user is User from here on
console.log(user.name);
} After if (!user) return, TS knows user can’t be null in the
rest of the function.
Up Next
Three more type-narrowing tools — instanceof, in, and explicit
guards.