043 — Type Narrowing with Type Guards, Control Flow and More (typeof to predicates)
Full companion narrative for Mostly Code on type narrowing: typeof, truthiness, Array.isArray, in, instanceof, type predicates, and the never else-branch
Video: 043 · Learn TypeScript: Type narrowing with Type Guards, Control flow and much more! · watch · ~14:30 · Mostly Code
Source: this is a written companion to the video. Part of the Favorite TypeScript Videos curated path, module 02 (type system). The survey lesson: JavaScript-built-in guards (
typeof, truthiness,Array.isArray,in,instanceof) into TypeScript-level guards (predicates, discriminated unions).
the union problem
A function takes string | number, and before any guard the editor only offers members common to both types. The fix is typeof narrowing — the first rung of the full JavaScript-built-in ladder this video walks.
typeof narrows primitives (and nothing else)
function myFunction(input: string | number) {
if (typeof input === "string") {
return input.replaceAll(" ", "_"); // string methods
} else {
return input.toPrecision(2); // number methods only
}
}



typeof null is "object" — use truthiness for nullable unions
typeof null returns "object", so a { item: string | null } | null parameter cannot be split with typeof alone. The video guards with a falsy check first (if (input && typeof input === "object")), since null is falsy.
Array.isArray for string | string[]
function f(input: string | string[]) {
if (Array.isArray(input)) {
return input.map((i) => i); // array methods
} else {
return "hello"; // plain string
}
}

in operator — key presence, even with undefined/null values
"surname" in myObject is false (absent key). "address" in myObject is true even though the value is undefined; "second_name" in myObject is true even though the value is null. Presence, not definedness. Then narrows myObject.address with ?? "" once presence is established.
instanceof for class instances (Date vs String object vs string)
input instanceof Date leads to toUTCString(); input instanceof String leads to boxed-string methods; the final else is the primitive string. Call sites: new Date(), new String("hello"), "hello".

predicates plus a name-discriminated union close the video
Glass (name: "glass", capacityMl) and ExteriorWindow (name: "exteriorWindow", doubleGlazing) share a base interface. const isGlass = (o: Glass | ExteriorWindow): o is Glass => o.name === "glass" narrows the consumer; the unreached final else infers never.
Covered: typeof narrowing for primitive unions, with else-branch inference; typeof null === "object" trap, fixed with a truthiness pre-check; Array.isArray, in (presence vs
definedness), instanceof across the ladder; type predicates over a shared name field, never
in the unreached else. Code frames embedded above: frame-0002.jpg–frame-0004.jpg (myFunction
typeof demo, [00:01:48]–[00:04:13]); frame-0005.jpg (input.map(f => f) array branch,
[00:05:26]); frame-0010.jpg (typeof + instanceof, [00:11:28]). Remaining frames
(frame-0001.jpg, frame-0006.jpg–frame-0009.jpg, frame-0011.jpg, frame-0012.jpg) are
title cards and transitional shots. Transcript notes: The auto-transcript mishears several terms;
this narrative uses the corrected forms: type narrowing (not “tap narrowing”), type string /
TypeScript (not “tab string/tab script”), falsy value (not “false value”), nullish coalescing
(??, not “new local operator”), and a function taking Date | String | string (not “my object
function”).
Prev: 042 — The any Type in TypeScript · Next: 044 — Type Narrowing in TypeScript