1function show(id: string | number) {2 if (typeof id === "string") {3 return id.toUpperCase();4 }5 return id.toFixed(1);6}7console.log(show("ab"), show(7));|union✓✗typeof id=== "string" ?$ node show.jsAB 7.0stringline 3numberline 5stringnumber.toUpperCase()"ab""AB"7"7.0"
Back: show.ts and the union tray (the type of id)Middle: the typeof check and its two lanes, string and numberTokens: the two types, then the values "ab" and 7 at run timeAt the back: show.ts and the type of id, a union holding two type tokens, string and number. In the middle: the typeof check, with a lane for each branch. In front: the console. The check splits the union: inside the if id is a string, after it id is a number. At run time "ab" and 7 take the same two paths, and the console prints AB 7.0. tsc shows this union as string | number. Order does not matter in a union: number | string is the same type.

A union

  1. A union type joins types with a bar. id: string | number means id can be a string or a number. Callers may pass either one, so show("ab") and show(7) are both allowed.

Narrowing

At run time

What happens

;; step 1/8 · union
show.ts
> 1  function show(id: string | number) {
  2      if (typeof id === "string") {
  3          return id.toUpperCase();
  4      }
  5      return id.toFixed(1);
  6  }
  7  console.log(show("ab"), show(7));

id: string | number
   either a string  ("ab")
   or a number      (7)

Working out the type from ifs, returns and other checks is called control flow analysis. It also narrows after ===, in, instanceof and Array.isArray checks. The program is real and runnable: tsc 5.9 compiles it with no errors and node show.js prints AB 7.0.