What Is Type Narrowing in TypeScript?
Learn how TypeScript type narrowing works — typeof, instanceof, and custom predicates — with a practical code example.
Expected Interview Answer
Type narrowing is the process by which TypeScript refines a broader, unioned type down to a more specific type within a block of code, based on runtime checks like typeof, instanceof, truthiness, or discriminant property comparisons.
When a variable has a union type such as `string | number`, TypeScript cannot let you call string-only methods on it directly, because it might be a number at that point. Once you write a guard like `if (typeof value === "string")`, the compiler tracks that check and treats `value` as `string` inside that branch, and as the remaining type outside it. Narrowing also works with `instanceof` for class checks, `in` for property existence, array `Array.isArray`, and custom type predicates written as `function isFoo(x: unknown): x is Foo`. This flow-sensitive analysis is what lets TypeScript feel ergonomic despite unions: you write ordinary runtime checks, and the type system silently follows along, updating the static type available at each point in the code without any extra casting.
- Eliminates the need for manual type casting after a runtime check
- Compiler enforces that every branch of a union is actually handled
- Custom type predicates let narrowing work with domain-specific validation logic
- Produces safer code paths since narrowed types prevent invalid method calls
AI Mentor Explanation
Type narrowing is like an umpire who initially treats a delivery as “possibly a no-ball or a fair ball” until checking the front-foot line, after which the delivery is definitively classified as one or the other for all subsequent decisions on that ball. Once the front-foot check happens, the umpire and scorers act only on the confirmed classification, never re-considering the other possibility for that delivery. Before the check, no LBW or run-out ruling can safely assume either category. That check-then-treat-as-confirmed pattern is exactly how TypeScript narrows a union type after a runtime guard.
Step-by-Step Explanation
Step 1
Start with a union type
A variable is declared with a broad type like string | number | null.
Step 2
Write a runtime guard
Use typeof, instanceof, in, Array.isArray, or a custom type predicate to test the value.
Step 3
Compiler tracks the branch
Inside the true branch, TypeScript treats the variable as the narrowed, more specific type.
Step 4
Type reverts outside the branch
Outside the guarded block, the variable returns to its original broader union type.
What Interviewer Expects
- Clear explanation of flow-sensitive type analysis tied to runtime checks
- Knowledge of multiple narrowing techniques: typeof, instanceof, in, custom predicates
- Understanding that narrowing is purely a compile-time concept with no runtime effect
- Ability to write a custom type predicate function (x is Foo)
Common Mistakes
- Believing narrowing changes runtime behavior rather than just static type checking
- Using type assertions (as Foo) instead of proper guards, bypassing real safety
- Forgetting that narrowing resets outside the guarded conditional block
- Not handling every branch of a union, leaving an unreachable-code assumption unchecked
Best Answer (HR Friendly)
“Type narrowing is how TypeScript gets smarter about a variable’s type as you check it in your code. If a value could be a string or a number, and you write a check for typeof value === "string", TypeScript then knows for sure it is a string inside that block, so you can safely use string-only methods without extra work.”
Code Example
type Input = string | number
function formatInput(value: Input): string {
if (typeof value === 'string') {
return value.trim().toUpperCase() // narrowed to string
}
return value.toFixed(2) // narrowed to number
}
interface ApiError { code: number; message: string }
function isApiError(x: unknown): x is ApiError {
return typeof x === 'object' && x !== null && 'code' in x && 'message' in x
}
function handle(result: unknown) {
if (isApiError(result)) {
console.log(result.message) // narrowed to ApiError
}
}Follow-up Questions
- How does discriminated union narrowing work with a shared literal tag field?
- What is the difference between a type assertion and a type predicate for narrowing?
- How does TypeScript narrow types inside a switch statement?
- Can narrowing survive across function calls, and why or why not?
MCQ Practice
1. What does type narrowing allow TypeScript to do?
Narrowing is a compile-time refinement of a union type based on a runtime guard the compiler tracks.
2. Which of these is a valid way to narrow a union type in TypeScript?
typeof, instanceof, in, Array.isArray, and custom predicates are standard narrowing techniques.
3. What is a custom type predicate function signature?
The `x is Foo` return type tells the compiler to narrow the argument when the function returns true.
Flash Cards
What is type narrowing? — Refining a union type to a more specific type within a runtime-checked branch.
Name three narrowing techniques. — typeof, instanceof, and custom type predicates (x is Foo).
Does narrowing affect runtime behavior? — No — it is purely a compile-time static analysis feature.
What happens to the type outside a narrowed branch? — It reverts to the original, broader union type.