Type Narrowing¶
Type narrowing is the process by which TypeScript refines a variable’s type based on runtime checks. You start with a wide type—like string | number—and TypeScript narrows it as you inspect it.
This is essential for working with:
- union types
- external data
- DOM events
- APIs
- optional fields
- discriminated unions
Let’s break down the main narrowing tools.
Narrowing with typeof¶
typeof is the simplest and most common narrowing operator.
Example: Narrowing a union¶
function printValue(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase()) // value: string
} else {
console.log(value.toFixed(2)) // value: number
}
}
TypeScript understands:
- inside the
if,valueis astring - inside the
else,valueis anumber
Supported typeof checks¶
You can narrow using:
"string""number""boolean""bigint""symbol""undefined""object""function"
Narrowing with instanceof¶
Use instanceof when working with classes or built‑in objects.
Example: Date vs string¶
function format(input: string | Date) {
if (input instanceof Date) {
return input.toISOString() // input: Date
}
return input.toUpperCase() // input: string
}
Common use cases¶
DateError- custom classes
- DOM elements (
HTMLElement,HTMLInputElement, etc.)
Narrowing with the in Operator¶
Use in when narrowing based on object properties.
Example: Two object shapes¶
type User = { name: string }
type Admin = { name: string; permissions: string[] }
function printInfo(person: User | Admin) {
if ("permissions" in person) {
console.log("Admin:", person.permissions)
} else {
console.log("User:", person.name)
}
}
TypeScript narrows based on the presence of a property.
Literal Types¶
Literal types represent exact values, not just general types.
Example: Literal strings¶
let direction: "up" | "down" | "left" | "right"
This is far safer than:
let direction: string
Literal numbers¶
type StatusCode = 200 | 400 | 404 | 500
Literal booleans¶
type Flag = true | false
Literal types are the foundation of discriminated unions.
Discriminated Unions¶
Discriminated unions are one of TypeScript’s most powerful features.
They combine:
- union types
- literal types
- narrowing
A discriminated union is a union of objects that all share a discriminant property—a literal field that identifies the variant.
Example: Shape types¶
type Circle = {
kind: "circle"
radius: number
}
type Square = {
kind: "square"
size: number
}
type Rectangle = {
kind: "rectangle"
width: number
height: number
}
type Shape = Circle | Square | Rectangle
Using a discriminated union¶
function area(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2
case "square":
return shape.size ** 2
case "rectangle":
return shape.width * shape.height
default:
const _exhaustive: never = shape
return _exhaustive
}
}
Why this is powerful¶
- TypeScript narrows based on
shape.kind - Each case gets full autocomplete
- Missing cases cause compile‑time errors
- Impossible states become impossible to represent
This is the pattern behind:
- Redux reducers
- API response modeling
- State machines
- UI component states
- Error handling
Putting It All Together¶
Here’s a realistic example combining all narrowing techniques:
type Result =
| { status: "success"; data: string }
| { status: "error"; error: Error }
| { status: "loading" }
function handle(result: Result) {
if (result.status === "success") {
console.log(result.data.toUpperCase())
} else if (result.status === "error") {
console.error(result.error.message)
} else {
console.log("Loading...")
}
}
TypeScript ensures:
dataonly exists on"success"erroronly exists on"error""loading"has neither- no unreachable or invalid states
This is the core of safe, expressive TypeScript.
Summary¶
In this lesson, you learned how TypeScript refines types at runtime using:
1. Narrowing operators¶
typeoffor primitivesinstanceoffor classesinfor object shapes
2. Literal types¶
- exact string/number/boolean values
- foundation for precise modeling
3. Discriminated unions¶
- the most powerful pattern in TypeScript
- safe, expressive, and exhaustive
- ideal for modeling real‑world state
These tools unlock TypeScript’s full potential and prepare you for advanced topics like custom type guards and exhaustive control flow analysis.