Skip to content

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.

Rajesh DhimanFebruary 08, 2026~24 min
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 never to enforce exhaustiveness.
Companion Book

Looking for structured, offline interview prep?

Free online lessons • Portable Amazon Kindle / Paperback

Frontend Interview Guide (2025 Edition) cover
Recommended for React & Frontend Rounds

Frontend Interview Guide (2025 Edition)

By Rajesh Dhiman

Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.

View on Amazon ↗Kindle & Paperback
JavaScript Interview Guide cover
Recommended for Core JS Rounds

JavaScript Interview Guide

By Rajesh Dhiman

Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.

View on Amazon ↗Kindle & Paperback

Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.

RD

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.