Lesson 8 of 80
A type guard is a promise you make to the compiler — a function whose return type tells TypeScript to actually trust and narrow, not just a boolean check.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
Two functions have identical logic, but one is typed `(x: unknown) => boolean` and the other `(x: unknown) => x is User`. What's the practical difference when used inside an if statement?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
The narrowing lesson covered inline checks like typeof and res.status === "success". A custom type guard packages a check into a reusable, named function — using a special return type, x is SomeType, called a type predicate.
function isUser(x: unknown): x is User {
return typeof x === "object" && x !== null && "id" in x && "name" in x;
}
function greet(data: unknown) {
if (isUser(data)) {
console.log(data.name); // ✅ narrowed to User inside this branch
}
}
The magic is entirely in the return type, x is User — not the logic itself. Write the exact same body but declare the return type as plain boolean instead, and greet would need a manual cast to access data.name, because TypeScript would have no reason to narrow anything.
function isUser(x: unknown): x is User {
return typeof x === "object" && x !== null && "id" in x; // 🐛 forgot to check "name"
}
This still compiles. TypeScript does not verify that your check logic actually matches User's real shape — a type predicate is a claim you make, not something the compiler audits. If this buggy guard returns true for something missing name, data.name will be undefined at runtime, and TypeScript won't have caught it — it trusted the promise x is User made.
function isString(x: unknown): x is string { return typeof x === 'string' } — a return type that narrows its argument.
x is T; it never re-derives or checks it against the actual runtime behavior.boolean-returning function with identical logic doesn't narrow anything, even though it's "doing the same check" at runtime — narrowing is a compile-time effect tied specifically to the x is T signature.filter narrowing only works because of that special overload. Most array methods (forEach, map) don't narrow their receiver's type the way filter does with a type-predicate callback."What's the difference between a function returning boolean and one returning x is T?" is a compact, precise way to check whether someone understands that TypeScript's narrowing is driven by declared signatures, not by actually verifying your logic — and that trust is exactly where a subtle, hard-to-catch bug can hide.