ESC

Type to search the knowledge base.

Type Guards typeof and instanceof

Built-in typeof and instanceof narrowing, custom type predicates, and how guards differ from assertions in TypeScript.

beginner3 min read
  • 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.

Docs: Narrowing, typeof.

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.

Further reading

Related guides