Skip to content
intermediatePhase 35 · TypeScript

Type Narrowing

Use type guards, typeof, instanceof, and discriminated unions for safe narrowing.

45m
0 problems
Topic Progress0%

typeof Guards

typeof Guards

The typeof operator checks the type of a value at runtime, allowing TypeScript to narrow the type.

Basic typeof

function process(value: string | number | boolean) {
  if (typeof value === "string") {
    // TypeScript knows value is string
    return value.toUpperCase();
  } else if (typeof value === "number") {
    // TypeScript knows value is number
    return value.toFixed(2);
  } else {
    // TypeScript knows value is boolean
    return value ? "yes" : "no";
  }
}

typeof with undefined

function greet(name: string | undefined) {
  if (typeof name === "string") {
    return `Hello, ${name}`;
  }
  return "Hello, stranger";
}

typeof in Callbacks

function handleEvent(event: KeyboardEvent | MouseEvent) {
  if (typeof event.key === "string") {
    // KeyboardEvent
    console.log(event.key);
  } else {
    // MouseEvent
    console.log(event.clientX);
  }
}

Limitations

typeof only works with primitives:

function process(value: string | object) {
  if (typeof value === "object") {
    // Can't distinguish between different object types
    // value is still string | object
  }
}

instanceof Guards

instanceof Guards

The instanceof operator checks if an object is an instance of a specific class.

Basic instanceof

class HttpError {
  constructor(public statusCode: number, public message: string) {}
}

class NetworkError {
  constructor(public message: string) {}
}

function handleError(error: HttpError | NetworkError) {
  if (error instanceof HttpError) {
    console.log(`HTTP ${error.statusCode}: ${error.message}`);
  } else {
    console.log(`Network: ${error.message}`);
  }
}

instanceof with Built-in Classes

function processDate(value: Date | string) {
  if (value instanceof Date) {
    return value.getFullYear();
  }
  return new Date(value).getFullYear();
}

instanceof with Error Types

function catchBlock(error: unknown) {
  if (error instanceof TypeError) {
    console.log("Type error:", error.message);
  } else if (error instanceof RangeError) {
    console.log("Range error:", error.message);
  } else if (error instanceof Error) {
    console.log("Generic error:", error.message);
  }
}

instanceof vs typeof

// Use typeof for primitives
function isString(value: unknown): value is string {
  return typeof value === "string";
}

// Use instanceof for classes
function isError(value: unknown): value is Error {
  return value instanceof Error;
}

Discriminated Unions

Discriminated Unions

Discriminated unions use a common property to narrow types in switch statements.

Pattern

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rectangle"; width: number; height: number }
  | { kind: "triangle"; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      // TypeScript knows shape is circle
      return Math.PI * shape.radius ** 2;
    case "rectangle":
      // TypeScript knows shape is rectangle
      return shape.width * shape.height;
    case "triangle":
      // TypeScript knows shape is triangle
      return (shape.base * shape.height) / 2;
  }
}

Exhaustive Switch

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rectangle":
      return shape.width * shape.height;
    case "triangle":
      return (shape.base * shape.height) / 2;
    default:
      const _exhaustive: never = shape;
      return _exhaustive;
  }
}

Real-World Example

type Action =
  | { type: "INCREMENT"; amount: number }
  | { type: "DECREMENT"; amount: number }
  | { type: "RESET" };

function reducer(state: number, action: Action): number {
  switch (action.type) {
    case "INCREMENT":
      return state + action.amount;
    case "DECREMENT":
      return state - action.amount;
    case "RESET":
      return 0;
  }
}

Practice Problems

0/3solved
Build Type Narrowing Component

Create a reusable React component implementing Type Narrowing. Include proper state management and accessibility.

Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where needed
Type Narrowing Testing

Write unit and integration tests for Type Narrowing using React Testing Library.

Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility tests
Type Narrowing Performance

Optimize Type Narrowing for performance. Consider memoization, code splitting, and bundle size.

Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysis

Quiz

1. What does typeof check?

Question 1 options

2. When should you use instanceof?

Question 2 options

3. What is a discriminated union?

Question 3 options

4. What does the never type do in a switch?

Question 4 options

Flashcards

Question

What does typeof guard check?

Answer

Primitive types: string, number, boolean, undefined, etc.

Question

When do you use instanceof?

Answer

To check if an object is an instance of a class

Question

What makes a discriminated union?

Answer

A common literal property that distinguishes between types

Question

How do you ensure exhaustive type checking?

Answer

Use the never type in a default switch case

Question

What is Type Narrowing?

Answer

Type Narrowing is a key concept in frontend development.

Revision Notes

Key Takeaways

  • 1.typeof guards narrow primitive types
  • 2.instanceof guards narrow class instances
  • 3.Discriminated unions use a common property for narrowing
  • 4.Exhaustive checking ensures all cases are handled
  • 5.Type narrowing happens automatically in control flow

Interview Tips

  • Explain when to use typeof vs instanceof
  • Show how discriminated unions simplify complex logic
  • Demonstrate exhaustive checking with never

Cheat Sheet

Cheat Sheet

typeof

if (typeof x === "string") {
  // x is string
}

instanceof

if (x instanceof Error) {
  // x is Error
}

Discriminated Union

type Shape =
  | { kind: "circle"; r: number }
  | { kind: "rect"; w: number; h: number };

switch (shape.kind) {
  case "circle": //...
  case "rect": //...
}

Exhaustive Check

default:
  const _: never = shape;
  return _;