Type Guards & Narrowing Patterns
How narrowing works in TypeScript: typeof/in, custom type guards, assertion functions, and practical patterns for safer code.
F
Frontend Interview Team
February 08, 2026
30‑second interview answer
TypeScript uses control-flow analysis to narrow types based on runtime checks (typeof, in, instanceof). For complex checks, write custom type guards (x is Foo) or assertion functions (asserts x is Foo). This turns unsafe unknown into safe, specific types.
Built-in narrowing
typeof
function print(x: string | number) {
if (typeof x === 'string') {
return x.toUpperCase();
}
return x.toFixed(2);
}in
type A = { a: string };
type B = { b: number };
function f(x: A | B) {
if ('a' in x) return x.a;
return x.b;
}instanceof
function handle(e: unknown) {
if (e instanceof Error) {
return e.message;
}
return String(e);
}Custom type guards
type User = { id: string; name: string };
function isUser(x: unknown): x is User {
return !!x && typeof x === 'object' && 'id' in x && 'name' in x;
}
const data: unknown = JSON.parse('{"id":"1","name":"Raj"}');
if (isUser(data)) {
data.name.toUpperCase(); // ✅
}Tip
Keep guards small and composable. For real validation, use Zod.
Assertion functions (asserts)
function assertIsError(e: unknown): asserts e is Error {
if (!(e instanceof Error)) {
throw new Error('Not an Error');
}
}
function logError(e: unknown) {
assertIsError(e);
console.log(e.stack);
}Common interview traps
- Mistaking runtime validation for compile-time types.
- Writing guards that are too weak (only checks one field).
- Overusing type assertions (
as Foo) instead of guards.
Mini Q&A
Q1: What does x is Foo mean?
- It’s a predicate telling TS that inside the true branch, x is Foo.
Q2: When use asserts?
- When you want to throw if the check fails and narrow afterwards.
Q3: Why prefer guards over as?
- Guards are safer;
ascan lie.
Summary checklist
- I can narrow with typeof/in/instanceof.
- I can write a type guard.
- I can use assertion functions.
- I avoid unsafe
as.