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.
F
Frontend Interview Team
February 08, 2026
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.