100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace

What Is Type Narrowing in TypeScript?

Learn how TypeScript type narrowing works — typeof, instanceof, and custom predicates — with a practical code example.

mediumQ206 of 224 in Web Development Est. time: 5 minsLast updated:
Open Code Lab

Expected Interview Answer

Type narrowing is the process by which TypeScript refines a broader, unioned type down to a more specific type within a block of code, based on runtime checks like typeof, instanceof, truthiness, or discriminant property comparisons.

When a variable has a union type such as `string | number`, TypeScript cannot let you call string-only methods on it directly, because it might be a number at that point. Once you write a guard like `if (typeof value === "string")`, the compiler tracks that check and treats `value` as `string` inside that branch, and as the remaining type outside it. Narrowing also works with `instanceof` for class checks, `in` for property existence, array `Array.isArray`, and custom type predicates written as `function isFoo(x: unknown): x is Foo`. This flow-sensitive analysis is what lets TypeScript feel ergonomic despite unions: you write ordinary runtime checks, and the type system silently follows along, updating the static type available at each point in the code without any extra casting.

  • Eliminates the need for manual type casting after a runtime check
  • Compiler enforces that every branch of a union is actually handled
  • Custom type predicates let narrowing work with domain-specific validation logic
  • Produces safer code paths since narrowed types prevent invalid method calls

AI Mentor Explanation

Type narrowing is like an umpire who initially treats a delivery as “possibly a no-ball or a fair ball” until checking the front-foot line, after which the delivery is definitively classified as one or the other for all subsequent decisions on that ball. Once the front-foot check happens, the umpire and scorers act only on the confirmed classification, never re-considering the other possibility for that delivery. Before the check, no LBW or run-out ruling can safely assume either category. That check-then-treat-as-confirmed pattern is exactly how TypeScript narrows a union type after a runtime guard.

Step-by-Step Explanation

  1. Step 1

    Start with a union type

    A variable is declared with a broad type like string | number | null.

  2. Step 2

    Write a runtime guard

    Use typeof, instanceof, in, Array.isArray, or a custom type predicate to test the value.

  3. Step 3

    Compiler tracks the branch

    Inside the true branch, TypeScript treats the variable as the narrowed, more specific type.

  4. Step 4

    Type reverts outside the branch

    Outside the guarded block, the variable returns to its original broader union type.

What Interviewer Expects

  • Clear explanation of flow-sensitive type analysis tied to runtime checks
  • Knowledge of multiple narrowing techniques: typeof, instanceof, in, custom predicates
  • Understanding that narrowing is purely a compile-time concept with no runtime effect
  • Ability to write a custom type predicate function (x is Foo)

Common Mistakes

  • Believing narrowing changes runtime behavior rather than just static type checking
  • Using type assertions (as Foo) instead of proper guards, bypassing real safety
  • Forgetting that narrowing resets outside the guarded conditional block
  • Not handling every branch of a union, leaving an unreachable-code assumption unchecked

Best Answer (HR Friendly)

Type narrowing is how TypeScript gets smarter about a variable’s type as you check it in your code. If a value could be a string or a number, and you write a check for typeof value === "string", TypeScript then knows for sure it is a string inside that block, so you can safely use string-only methods without extra work.

Code Example

Narrowing a union type with typeof and a custom predicate
type Input = string | number

function formatInput(value: Input): string {
  if (typeof value === 'string') {
    return value.trim().toUpperCase() // narrowed to string
  }
  return value.toFixed(2) // narrowed to number
}

interface ApiError { code: number; message: string }

function isApiError(x: unknown): x is ApiError {
  return typeof x === 'object' && x !== null && 'code' in x && 'message' in x
}

function handle(result: unknown) {
  if (isApiError(result)) {
    console.log(result.message) // narrowed to ApiError
  }
}

Follow-up Questions

  • How does discriminated union narrowing work with a shared literal tag field?
  • What is the difference between a type assertion and a type predicate for narrowing?
  • How does TypeScript narrow types inside a switch statement?
  • Can narrowing survive across function calls, and why or why not?

MCQ Practice

1. What does type narrowing allow TypeScript to do?

Narrowing is a compile-time refinement of a union type based on a runtime guard the compiler tracks.

2. Which of these is a valid way to narrow a union type in TypeScript?

typeof, instanceof, in, Array.isArray, and custom predicates are standard narrowing techniques.

3. What is a custom type predicate function signature?

The `x is Foo` return type tells the compiler to narrow the argument when the function returns true.

Flash Cards

What is type narrowing?Refining a union type to a more specific type within a runtime-checked branch.

Name three narrowing techniques.typeof, instanceof, and custom type predicates (x is Foo).

Does narrowing affect runtime behavior?No — it is purely a compile-time static analysis feature.

What happens to the type outside a narrowed branch?It reverts to the original, broader union type.

1 / 4

Continue Learning