TypeScript Narrowing & Discriminated Unions (Interview-Ready)
A deep, interview-ready guide to narrowing in TypeScript: typeof, in, instanceof, user-defined type guards, and discriminated unions for safe, expressive code.
On this page
Narrowing is how TypeScript lets you safely work with unions.
Interviewers love this topic because it shows whether you can write safe code without overusing type assertions.
1) What is narrowing?
If a value has a union type:
type Id = string | number;TypeScript needs checks to decide which branch you’re in.
2) typeof narrowing
function format(id: string | number) {
if (typeof id === 'string') {
return id.toUpperCase();
}
return id.toFixed(2);
}3) in operator narrowing
type A = { a: string };
type B = { b: number };
type X = A | B;
function f(x: X) {
if ('a' in x) {
return x.a;
}
return x.b;
}4) instanceof narrowing
function f(err: unknown) {
if (err instanceof Error) {
return err.message;
}
return String(err);
}5) User-defined type guards
type User = { id: string; name: string };
function isUser(x: any): x is User {
return x && typeof x.id === 'string' && typeof x.name === 'string';
}
function handle(x: unknown) {
if (isUser(x)) {
x.name; // typed
}
}This is a strong interview pattern.
6) Discriminated unions (most important)
Discriminated unions use a shared "tag" field.
type ApiResult<T> =
| { ok: true; data: T }
| { ok: false; error: string };
function handle<T>(res: ApiResult<T>) {
if (res.ok) {
return res.data;
}
return res.error;
}Because ok is the discriminant, TS narrows automatically.
Another example
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; size: number };
function area(s: Shape) {
switch (s.kind) {
case 'circle':
return Math.PI * s.radius * s.radius;
case 'square':
return s.size * s.size;
}
}7) Exhaustiveness checking
Add a never check to ensure all cases are handled.
function assertNever(x: never): never {
throw new Error('Unhandled case');
}
function area(s: Shape) {
switch (s.kind) {
case 'circle':
return Math.PI * s.radius * s.radius;
case 'square':
return s.size * s.size;
default:
return assertNever(s);
}
}Interviewers love this.
Summary
- Narrowing makes unions safe.
- Use typeof/in/instanceof.
- Prefer discriminated unions for structured results.
- Use
neverto enforce exhaustiveness.
Looking for structured, offline interview prep?
Free online lessons • Portable Amazon Kindle / Paperback
Frontend Interview Guide (2025 Edition)
By Rajesh Dhiman
Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.
JavaScript Interview Guide
By Rajesh Dhiman
Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.
Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.
Written by Rajesh Dhiman
Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.