Skip to content

typeof and instanceof Guards

Narrowing is TypeScript’s ability to refine a type within a conditional branch. When you check what kind of value you have, TypeScript updates its understanding of the type for the rest of that branch. The two most fundamental narrowing tools are typeof for primitives and instanceof for class instances.

typeof returns a string describing the runtime type of a value. TypeScript understands every possible return value and narrows accordingly:

function display(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase(); // TypeScript knows: value is string
}
return value.toFixed(2); // TypeScript knows: value is number
}

Inside the typeof === 'string' branch, value is narrowed to string. In the else branch (or after the if), value is narrowed to number — the remaining possibility.

The values typeof can return and the types they narrow to:

typeof resultNarrows to
'string'string
'number'number
'boolean'boolean
'undefined'undefined
'function'Function
'object'object | null (note: typeof null === 'object')
'symbol'symbol
'bigint'bigint

The null caveat matters: typeof null === 'object', so a typeof value === 'object' check does not rule out null. Always pair it with a null check when narrowing to a non-null object.

instanceof checks whether an object was created by a particular constructor. TypeScript narrows to the class type:

class QuizError extends Error {
constructor(public code: number, message: string) {
super(message);
}
}
function handleError(err: Error): void {
if (err instanceof QuizError) {
console.log('Quiz error code:', err.code); // TypeScript knows: err is QuizError
} else {
console.log('General error:', err.message); // TypeScript knows: err is Error
}
}

Inside the instanceof QuizError branch, err is narrowed to QuizError, giving access to err.code. Outside the branch, it remains Error.

In TypeScript with strict: true, caught errors in a try/catch block are typed as unknown — because anything can be thrown, not just Error instances:

try {
const data = await fetchJson<ApiResponse>(url);
} catch (err) {
// err: unknown — could be an Error, a string, or anything else
console.log(err.message); // Error: 'err' is of type 'unknown'
}

Use instanceof Error to narrow before accessing Error properties:

try {
const data = await fetchJson<ApiResponse>(url);
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown error';
alert(`Could not load questions: ${message}`);
}

This pattern appears in every catch block in AceIt’s main.ts. The instanceof Error check is not just a TypeScript formality — it handles the real possibility that something other than an Error was thrown.

typeof is also the natural tool for narrowing optional parameters — which are T | undefined:

function buildUrl(amount: number, difficulty?: Difficulty): string {
let url = `https://opentdb.com/api.php?amount=${amount}`;
if (typeof difficulty !== 'undefined') {
url += `&difficulty=${difficulty}`; // TypeScript knows: difficulty is Difficulty
}
return url;
}

The truthiness check (if (difficulty)) is more concise and equally valid for non-empty strings, but typeof !== 'undefined' is explicit and avoids accidentally treating empty strings or 0 as absent.

In a scratch file narrowing-practice.ts:

  1. Write a function safeCatch(fn: () => void): string that calls fn inside a try/catch and returns err instanceof Error ? err.message : 'Unknown error'. Call it with a function that throws a string and one that throws an Error — confirm both are handled.
  2. Write describeValue(value: string | number | boolean): string that uses typeof to return a description like 'string: AceIt', 'number: 10', or 'boolean: true'.
  3. Try accessing .message on the caught value without the instanceof guard — read the error TypeScript reports.

You will apply the instanceof Error pattern to every catch block in main.ts when you write it in Module 07.

  • typeof narrows primitive types: 'string', 'number', 'boolean', 'undefined', 'function'.
  • typeof null === 'object' — pair an object check with a null check when needed.
  • instanceof narrows class instances — the value is refined to the specific class type inside the branch.
  • Caught errors are unknown in strict TypeScript — always narrow with instanceof Error before accessing error properties.
  • Truthiness checks narrow out undefined, null, 0, and '' — use typeof !== 'undefined' when you need precision.