Type Narrowing with Conditionals in TypeScript
Master Type Narrowing with Conditionals to write safer, more predictable code. Learn how TypeScript's control flow analysis refines your types automatically.

Previously in this course, we explored Union Types for Flexibility in TypeScript and how to constrain them using Literal Types for Status Codes: Enforcing Constraints in TypeScript. While unions provide the flexibility to handle multiple data shapes, they introduce a challenge: how do we tell the compiler which specific type we are dealing with at any given moment?
This is where Type Narrowing comes in. By using standard JavaScript control flow—like if statements—TypeScript analyzes your code and automatically "narrows" a broad union type down to a specific, usable type.
Understanding Control Flow Analysis
TypeScript is smart enough to trace the path your code takes. When you perform a check inside a conditional block, the compiler realizes that the variable inside that block can only be a specific member of the union.
Think of it as a funnel. You start with a wide "Union" type (e.g., string | number), and as you add conditional logic, you shrink that type until you have exactly what you need.
A Concrete Example: Processing Task IDs
In our ongoing Task API client project, a task ID might come in as either a string (from a URL parameter) or a number (from our database). We need to handle both gracefully.
TYPESCRIPTtype TaskId = string | number; function processTaskId(id: TaskId) { // At this point, CE9178">'id' is still string | number if (typeof id === CE9178">'string') { // TypeScript knows CE9178">'id' is a string here! console.log(CE9178">`Processing string ID: ${id.toUpperCase()}`); } else { // TypeScript knows CE9178">'id' must be a number here console.log(CE9178">`Processing numeric ID: ${id.toFixed(0)}`); } }
In the example above, the typeof check serves as a type guard. Without that if block, calling .toUpperCase() on a number would cause a compilation error. By adding the conditional, we provided the compiler with the proof it needed to allow the operation.
Why Safety Matters

Type Narrowing isn't just about satisfying the compiler; it's about runtime safety. If you accidentally attempt to perform an operation that doesn't exist on one of the union types, TypeScript will stop you before you even run your code.
| Approach | Type Safety | Runtime Risk |
|---|---|---|
| Ignoring types | None | High (runtime crashes) |
Casting (as string) | Low (manual, error-prone) | Medium |
| Type Narrowing | High (verified by compiler) | Minimal |
Hands-on Exercise
Let's apply this to our Task API client. Imagine a function that handles an API response that can either be a Task object or a string error message.
- Create a
Taskinterface with anidandtitle. - Define a type alias
ApiResponseasTask | string. - Write a function
displayResponse(response: ApiResponse)that checks if the response is a string. If it is, log the error; otherwise, log the task title.
Hint: Remember that typeof works on primitives, but you can also check for properties if the union contains objects!
Common Pitfalls
Even experienced developers run into these issues when narrowing types:
- Forgetting the
elsebranch: If you check for one type in anifblock, TypeScript assumes the "remaining" types exist in theelseblock. If you aren't exhaustive, you might miss a potentialundefinedornullcase. - Over-complicating checks: You don't need complex logic for simple types. Stick to
typeoffor primitives. - Assuming properties exist: Accessing a property like
response.titlebefore checking ifresponseis an object will result in a compiler error. Always narrow the type before accessing properties specific to that type.
FAQ
Can I use switch statements for narrowing?
Yes! switch statements are excellent for narrowing, especially when working with union types that rely on a common property (like a status string).
What if I have a union of three or more types?
You can chain if/else if statements. TypeScript will continue to narrow the type with each condition until the final else block, which will contain whatever is left of the original union.
Does this work with null or undefined?
Absolutely. Checking if (value !== null) is a standard way to narrow a type from string | null to string.
Recap

Type Narrowing with conditionals turns broad, ambiguous types into precise, safe ones. By using standard JavaScript control flow, we allow TypeScript's engine to track our logic and guarantee that we only call methods or access properties that actually exist on the current object. This leads to cleaner code and fewer runtime bugs in our API client.
Up next: We will dive deeper into the typeof operator and learn how to write more sophisticated type guards.
Work with me

Next.js Full-Stack Web App Development
A fast, SEO-ready full-stack web app built with Next.js 16 — from idea to deployed product, by an engineer who ships to production.

React & Next.js Dashboard / Admin UI Development
A clean, data-rich dashboard UI in React or Next.js — charts, tables, and real-time data that your users will actually enjoy using.


