Type Guards typeof and instanceof
Built-in typeof and instanceof narrowing, custom type predicates, and how guards differ from assertions in TypeScript.
- typescript
- type-guards
A type guard is a runtime check that TypeScript understands for narrowing. Built-ins: typeof, instanceof, equality, in, Array.isArray. User-defined: functions returning value is Type.
typeof guards
function padLeft(value: string | number, padding: string | number) {
if (typeof padding === 'number') {
return ' '.repeat(padding) + value;
}
return padding + value;
}
typeof results TypeScript models: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function".
Caveat: typeof null === 'object' — exclude null separately.
function print(x: string | null) {
if (typeof x === 'object') {
// x is null here (only null from that union is typeof object)
}
}
instanceof guards
function getDateString(d: Date | string) {
if (d instanceof Date) {
return d.toISOString();
}
return d;
}
Works for class instances and many DOM types:
function valueOf(el: EventTarget | null) {
if (el instanceof HTMLInputElement) {
return el.value;
}
return undefined;
}
Caveat: instanceof fails across realms/iframes and some structured-clone boundaries. Prefer duck typing or brands when crossing windows.
Array.isArray
function first(input: string | string[]) {
if (Array.isArray(input)) return input[0];
return input;
}
typeof [] === 'object' — always use Array.isArray for arrays.
User-defined type predicates
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
const pets: (Fish | Bird)[] = [];
pets.filter(isFish).forEach((f) => f.swim());
The predicate must be honest — TypeScript trusts the boolean you return.
Assertion functions
function assertIsString(v: unknown): asserts v is string {
if (typeof v !== 'string') throw new Error('not a string');
}
After the call, v is string. See also never type.
Equality narrowing
function example(x: string | number, y: string | boolean) {
if (x === y) {
// x and y are string
}
}
Control flow and early returns
function handle(user: User | null) {
if (!user) return;
console.log(user.name);
}
Truthy checks narrow away null / undefined / '' carefully — empty string is falsy.
Footguns
| Guard | Pitfall |
|---|---|
typeof |
null is 'object'; no typeof for class instances |
instanceof |
Cross-iframe Array / Date issues |
| Predicates | Lying predicates poison the checker |
in |
Prototype chain; optional props still undefined |
Prefer discriminated unions when you design the data.
Interview out-loud answer
“Type guards are runtime checks that narrow types. typeof handles primitives, instanceof classes/DOM, Array.isArray for arrays. Custom predicates use value is T. Guards check; assertions only silence the compiler.”
Narrowing in callbacks
TypeScript sometimes loses narrowing inside nested function callbacks. Assign to a const after the guard or use a predicate function so the nested closure sees the refined type. Don’t “fix” with assertions if a local const works.
Extra practice
Write a minimal demo in a scratch file or the playground: one happy path, one failure path, and one boundary input. If you cannot exhibit a bug that the pattern prevents, you do not own the concept yet — re-read the primary docs linked below and tighten the example until the failure is obvious.
Related on this site
- Type narrowing
- Narrowing with in operator
- unknown vs any
- Discriminated unions for UI state
- Type assertions safely
Further reading
Related guides
- Basic Types and AnnotationsPrimitives, arrays, objects, and function annotations TypeScript actually checks — plus where inference is enough and where it is not.
- Branded Types for IDsNominal-style UserId vs OrderId in TypeScript — prevent ID mixups at compile time with brands, parsers, and form boundaries.
- Conditional Types IntroT extends U ? X : Y — how TypeScript picks types from conditions, distributes over unions, and powers utility types you already use.
- Declaration Files and DefinitelyTypedHow .d.ts files describe JS to TypeScript, when to use @types packages, module augmentation, and writing minimal ambient types for untyped libs.
- Discriminated Unions for UI StateModel loading, success, and error as mutually exclusive variants so TypeScript and your UI cannot show impossible states.