Study notes · 10% of the exam

Narrowing and Control-Flow Analysis

Narrowing is how TypeScript refines a union to the members that can reach a point in your code. It reads ordinary JavaScript checks, so knowing which checks it understands, and where it stops trusting them, is the core skill.

Key points

  1. 1

    Built-in guards: typeof, instanceof, in, ===/!== (including against literals), truthiness and Array.isArray. Each narrows both branches, and early returns narrow the rest of the function.

  2. 2

    A discriminated union has a literal-typed tag on every member. Comparing the tag (in if, switch or switch (true) since 5.3) narrows the whole object; const destructuring keeps that link (4.6).

  3. 3

    Exhaustiveness: in default, assign the value to never or write value satisfies never, so adding a union member becomes a compile error.

  4. 4

    User-defined guards (x is T) and assertion functions (asserts x is T) are trusted, not verified. Since 5.5 many simple arrow guards are inferred, but only when true means T and false means not-T.

  5. 5

    Narrowing is kept in closures for const, and since 5.4 for let and parameters with no assignment after the closure is created. Hoisted function declarations do not keep it.

  6. 6

    Property narrowing (if (r.data !== null)) survives function calls even though a call might mutate the object. That is a deliberate, unsound trade-off.

  7. 7

    Values of a type parameter narrow by intersection (T & string), and in on object adds Record<"key", unknown> (4.9).

Common traps

  • if (value) also rejects 0, "" and NaN. Compare with undefined or null explicitly when those are valid values.

  • Array.isArray on string | readonly string[] gives any[] in the true branch and does not narrow the else branch.

  • A predicate such as n is number that also checks n > 0 sends negative numbers into the branch typed as string.

Test yourself on Narrowing and Control-Flow Analysis

Ten questions, with the answer and explanation after each one.