Type guards and assertion functions: teaching TypeScript what you checked
Write x is T type predicates and asserts functions, know when TypeScript 5.5 infers predicates for you, and filter arrays to the right type, without lying to the compiler.
Comes up in: Coding
Learn#
Zoya’s API returns unknown JSON. She writes const user = data as User and ships. A month later the API renames name to fullName, and the app crashes on user.name.toUpperCase(). The compiler had no way to warn her.
A type predicate is a return type such as x is User. The function does real checks at runtime; when it returns true, TypeScript narrows the value. Since TypeScript 5.5, simple checks infer a predicate on their own:
type User = { id: number; name: string };
const cache = new Map<number, User>([[3, { id: 3, name: "Asha" }]]);
const maybe = [cache.get(3), cache.get(7)]; // (User | undefined)[]
const a = maybe.filter((u) => u !== undefined); // User[]: TypeScript infers a type predicate
const b = maybe.filter((u) => Boolean(u)); // still (User | undefined)[]
a[0].name;
b[0].name;
function isUser(x: unknown): x is User {
return typeof x === "object" && x !== null && "id" in x && "name" in x;
}
const raw: unknown = JSON.parse('{"id": 1, "name": "Ravi"}');
if (isUser(raw)) raw.name.toUpperCase(); // narrowed to Userexample.ts(8,1): error TS2532: Object is possibly 'undefined'.u !== undefined is a check TypeScript understands, so it infers the predicate. Boolean(u) is not.
State
- raw: unknown
raw comes from JSON.parse, so its type is unknown. You cannot read raw.name yet.
Read the steps as text
- raw comes from JSON.parse, so its type is unknown. You cannot read raw.name yet.
- isUser(raw) runs real checks at runtime and returns true or false.
- Its return type is raw is User, so inside the if, TypeScript treats raw as a User.
- After the if, raw is unknown again. The narrowing lasts only where the check holds.
Predict#
Predict first
function isString(x: unknown): x is string { return true } — does TypeScript complain?
TypeScript does not check that the body proves the predicate; it trusts you. A wrong guard is as unsafe as as. People expect a check because TypeScript checks almost everything else.
Interview angle#
Say it like this — about 30 seconds
“A user-defined type guard returns x is T. It runs real runtime checks, and when it returns true, TypeScript narrows the argument. TypeScript does not verify the body, so a wrong guard lies to the compiler. Since TypeScript 5.5, simple arrow functions like u !== undefined infer the predicate, which makes filter return the narrowed array type. Assertion functions use asserts x is T: they throw instead of returning false, and the code after the call is narrowed.”
Follow-up Type guard or assertion function: when do you use each?
Follow-up Why must an assertion function be called through an explicit type?
Follow-up How do you validate a large API response?
Practice#
Guided
Give each function the right TypeScript signature and a real runtime check.
Done when
isNonNullableis a predicate (value is NonNullable<T>) and keeps 0 and "".isProductchecks the types of both fields, and rejects a price of NaN.assertIsDefinedusesasserts value is NonNullable<T>and throws “label is missing”.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
What return type makes a function a type guard?
A type predicate: x is T.
Does TypeScript check that a type guard's body is correct?
No. It trusts the predicate; a wrong guard is unsafe.
Which TypeScript version infers type predicates for filter callbacks?
TypeScript 5.5.
What does asserts value is T mean?
The function throws unless value is T; after the call, value is narrowed.
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
filter has an overload that accepts a predicate and returns S[]. Without a predicate, it returns the original element type. Write .filter(isNonNullable) or a simple inferred check such as x => x !== null.
Check yourself: What does the filter callback need so the result type is narrowed?
A type predicate, written or inferred.
How it works underneathSenior rounds
x is T when the body is a narrowing expression and “false” truly means “not T”. It does not infer for truthiness checks such as !!x, because 0 and "" are falsy but not null Source: TypeScript 5.5: Inferred type predicates.Check yourself: When does TypeScript 5.5 infer a predicate?
When the function returns a boolean check that narrows its parameter, and the false case narrows the opposite way.
Edge cases and trapsWhere people slip
asserts value is T narrows the argument after the call.asserts cond narrows whatever cond would, like Node’s assert(x !== null) Source: TypeScript 3.7: Assertion functions.void and be called through a name with an explicit type.Check yourself: What does asserts cond (without is) narrow?
Whatever the condition expression narrows, as if it were in an if.
In productionStaff-level answers
Put guards where outside data enters: API responses, localStorage, URL parameters, postMessage. Inside the app, the types can then be trusted. For big shapes, a schema library generates both the check and the type, so they never drift apart.
Check yourself: Where should runtime guards sit in an app?
At the boundaries: API responses, storage, URL params and messages.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.