Type Narrowing & Type Guards
Type narrowing is how TypeScript refines a broad type into a more specific one inside a block — using runtime checks like typeof , instanceof , and custom type guards — so you can use the value safely.
Learn Type Narrowing & Type Guards in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
A union type is like a parcel labelled "fragile or heavy". You can't decide how to lift it until you check which it is. Narrowing is that check at the door: once you confirm "this one's fragile", you handle it with care for the rest of the journey. TypeScript watches your checks and updates what it knows about the parcel inside each branch.
1. typeof & Truthiness Narrowing
The simplest narrowing tool is typeof , which distinguishes the primitive types ( "string" , "number" , "boolean" , …). Inside if (typeof x === "number") , TypeScript knows x is a number . A bare truthiness check like if (name) is just as useful: it removes null , undefined , and "" from the type, so the value inside the block is guaranteed present.
2. instanceof & in Narrowing
For class instances, instanceof narrows to the class: inside if (animal instanceof Cat) you can call Cat -only methods. For plain object shapes that don't share a class, the in operator checks for a distinguishing property — "radius" in shape tells a circle from a rectangle and narrows the union accordingly.
3. Custom Type Guards & Discriminants
When the built-in checks aren't enough, write your own type guard : a function whose return type is the predicate arg is SomeType . When it returns true , TypeScript narrows the argument to that type wherever you called it. The most ergonomic pattern of all is a discriminant check — switching on a shared literal field ( kind ) so each branch knows the exact member.
🎯 Your Turn
Write len , which returns the length of a string or array and 0 for anything else. Fill in the two blanks marked ___ , then run it.
No blanks this time — just a brief and a starting outline. Write the type guard and the handler yourself, run it, and check your output against the example in the comments.
Practice quiz
What is type narrowing?
- Shrinking a string's length
- Removing a type alias
- Refining a broad type to a more specific one inside a block based on a runtime check
- Deleting union members permanently
Answer: Refining a broad type to a more specific one inside a block based on a runtime check. After a check like typeof x === 'string', TypeScript treats x as string in that branch.
Which narrowing tool is best for primitive types?
- typeof
- instanceof
- the 'in' operator
- Array.isArray
Answer: typeof. typeof distinguishes 'string', 'number', 'boolean', and other primitives.
What does a truthiness check like 'if (name)' remove from the type?
- Only null
- Only undefined
- Nothing
- null, undefined, and falsy values like '' and 0
Answer: null, undefined, and falsy values like '' and 0. It rules out all falsy values, including null, undefined, '', and 0.
Which operator narrows class instances?
- typeof
- instanceof
- in
- is
Answer: instanceof. if (animal instanceof Cat) lets you call Cat-only methods in that branch.
What does the 'in' operator check during narrowing?
- Whether a property exists on an object
- Whether a value is in an array
- Whether a class extends another
- The type of a primitive
Answer: Whether a property exists on an object. 'radius' in shape distinguishes a circle from a rectangle by a property.
What makes a function a custom type guard?
- It returns boolean
- It uses instanceof
- Its return type is a predicate like 'x is Foo'
- It is named with 'is'
Answer: Its return type is a predicate like 'x is Foo'. The 'arg is Type' return type tells the compiler to narrow when the guard returns true.
If a guard returns plain 'boolean' instead of 'x is Foo', what happens?
- It still narrows
- TypeScript does NOT narrow the variable
- It errors
- It narrows to never
Answer: TypeScript does NOT narrow the variable. Without the predicate, the compiler sees a boolean and won't narrow, even with identical runtime logic.
Why use Array.isArray(x) instead of typeof x === 'object' to detect arrays?
Arrays report 'object' under typeof, so Array.isArray is the specific check.
What is the cleanest narrowing for a union sharing a literal 'kind' field?
- Many instanceof checks
- A discriminant check - switch on the 'kind' field
- as assertions
- A try/catch
Answer: A discriminant check - switch on the 'kind' field. Switching on a shared discriminant field gives the compiler precise per-branch types.
Why is 'if (count)' risky when 0 is a valid value?
- It throws on 0
- 0 becomes a string
- Truthiness skips 0 along with null/undefined
- It always returns false
Answer: Truthiness skips 0 along with null/undefined. Truthiness drops 0; use 'if (count !== undefined)' when zero is legitimate.