Engrain by ByteRush

TS.02.03 Mid-level· 10 minCore — asked often

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 User
TypeScript compiler output (verified)
example.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.

A type guard narrows only inside the checkStep 1 of 4

State

  • raw: unknown

raw comes from JSON.parse, so its type is unknown. You cannot read raw.name yet.

Read the steps as text
  1. raw comes from JSON.parse, so its type is unknown. You cannot read raw.name yet.
  2. isUser(raw) runs real checks at runtime and returns true or false.
  3. Its return type is raw is User, so inside the if, TypeScript treats raw as a User.
  4. 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?

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.”

Stays on your device. Nothing is uploaded.
Follow-up Type guard or assertion function: when do you use each?
A guard when both outcomes are normal and you branch on them. An assertion when a failure is a bug, and you want to stop at once.
Follow-up Why must an assertion function be called through an explicit type?
TypeScript needs every name in the call to have an explicit type annotation, so it can see the asserts signature during control-flow analysis.
Follow-up How do you validate a large API response?
Use a schema library, such as Zod or Valibot, which gives you a guard and a type from one definition.

Practice#

Guided

Give each function the right TypeScript signature and a real runtime check.

Done when

  • isNonNullable is a predicate (value is NonNullable<T>) and keeps 0 and "".
  • isProduct checks the types of both fields, and rejects a price of NaN.
  • assertIsDefined uses asserts value is NonNullable<T> and throws “label is missing”.
Your solution
Ctrl/⌘ + Enter

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
TypeScript 5.5 infers 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.
Assertion functions must return 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.