Suggest an editImprove this articleRefine the answer for “What is typeguard in TypeScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Type guard** is a runtime check that narrows a union type to a specific subtype inside a code block. ```typescript function isUser(obj: unknown): obj is { name: string } { return typeof obj === "object" && obj !== null && "name" in obj; } if (isUser(data)) { console.log(data.name); // TypeScript knows the shape here } ``` **Key point:** the `is` keyword in the return type is what triggers TypeScript's narrowing. A plain `boolean` return does nothing.Shown above the full answer for quick recall.Answer (EN)Image**Type guard** is a runtime check that tells the TypeScript compiler the exact type of a value inside a specific code block, narrowing a [union type](/questions/union-types-typescript) to a more precise subtype. ## Theory ### TL;DR - Think of type guards like a security checkpoint: past the check, TypeScript knows exactly what's inside - Without guards, TypeScript treats `string | number` as both at once; with a guard, each branch gets a single precise type - Built-in options: `typeof` for primitives, `instanceof` for class instances, `in` for property existence - User-defined guard: a function returning `param is SpecificType`, where the `is` keyword is not optional - Rule of thumb: working with union types or `unknown`/`any`? Add a guard before accessing type-specific properties ### Quick example ```typescript function printLength(value: string | number) { if (typeof value === "string") { // TypeScript knows value is string here console.log(value.length); // works } else { // TypeScript knows value is number here console.log(value.toFixed(2)); // works } } printLength("hello"); // 5 printLength(3.14); // "3.14" ``` `typeof value === "string"` is the guard. TypeScript reads this check and updates its type knowledge for that branch only. No casting, no errors. ### Key difference Without a guard, TypeScript refuses to let you call `.length` on `string | number` because it cannot guarantee the type at runtime. A guard gives the compiler proof: inside this block, I checked. The narrowing lives only in that scope, once you exit the branch, the union type is back. ### When to use - **Union type params** - add `typeof` or `instanceof` before calling type-specific methods - **API responses typed as `unknown`** - guard before accessing any property like `user.name` - **Polymorphic components** - use `"onClick" in props` to distinguish `ButtonProps` from `LinkProps` - **Skip it** when working with a single concrete type, TypeScript already knows - **Prefer over `as` casting** - casting skips the check; a guard proves the type at runtime ### How the compiler handles this TypeScript performs control flow analysis during type checking. It tracks possible types through every code path, recognizes built-in guards by their pattern, and intersects the original union with the asserted subtype inside that branch. User-defined guards work because of the `is` return type: TypeScript reads `param is SpecificType` as a contract and applies narrowing at every call site. No extra runtime cost beyond the check itself, V8 just executes the JavaScript normally. ### Common mistakes **Mistake 1: using `in` but calling the wrong method** ```typescript interface Cat { meow(): void; } interface Dog { bark(): void; } function pet(animal: Cat | Dog) { if ("meow" in animal) { animal.bark(); // Error: Cat doesn't have bark } } ``` `in` narrows by property existence. It confirms `meow` is there, not that `bark` is. Call what you actually checked for, or use a [discriminated union](/questions/discriminated-union-typescript) with a literal `type` field instead. **Mistake 2: forgetting the `is` type predicate** This is the one that shows up most often in code reviews. The function looks correct, but the narrowing stops working silently. ```typescript // Returns boolean - no narrowing happens function isString(val: any): boolean { return typeof val === "string"; } if (isString(x)) { x.length; // TypeScript still sees any here } ``` TypeScript ignores plain `boolean` returns for narrowing purposes. The fix is one word: ```typescript function isString(val: any): val is string { return typeof val === "string"; } ``` **Mistake 3: `instanceof` on string primitives** ```typescript if (x instanceof String) { // Checks wrapper object, not primitive x.toUpperCase(); } ``` Primitives are not instances of anything. Use `typeof x === "string"` for strings, numbers, and booleans. **Mistake 4: mutating the variable inside the guard block** ```typescript let data: string | number = "test"; if (typeof data === "string") { data = 42; // reassignment resets narrowing } data.toFixed(); // Compiler error - data is string | number again ``` Reassignment throws off TypeScript's control flow tracking. Use a `const` local variable inside the block if you need to transform the value. ### Real-world usage - **React** - Mantine UI uses `"onClick" in props` to render a `<button>` or `<a>` from a single polymorphic component - **Express** - `typeof req.body === "object"` before accessing body properties in middleware - **Zod** - `schema.safeParse(data).success` narrows the result to the inferred type automatically - **Next.js** - `req.method === "POST"` narrows the method union before parsing the request body - **Guard vs `as`** - use guards for data from APIs or user input; use `as` only for trusted static shapes where you're certain ### Follow-up questions **Q:** What is a user-defined type guard? **A:** A function that returns `param is SpecificType`. TypeScript uses the `is` predicate to narrow the type at every call site. Example: `function isUser(obj: any): obj is User { return typeof obj.name === "string"; }`. **Q:** What is the difference between `in`, `typeof`, and `instanceof`? **A:** `typeof` checks primitive types: string, number, boolean, function. `instanceof` checks class instances. `in` checks if a property exists on an object, which is useful for discriminating interfaces that share no common base class. **Q:** How does TypeScript handle nested type guards? **A:** Control flow analysis tracks types through nested `if` blocks, intersecting types progressively. The deeper the nesting, the narrower the type in each branch. **Q:** Why does narrowing sometimes break inside loops? **A:** TypeScript resets narrowing on each iteration because the variable might be reassigned between runs. Capture the narrowed value in a `const` inside the loop body to keep it stable. **Q:** What is a discriminated union and how does it relate to type guards? (senior level) **A:** A discriminated union uses a shared literal field like `type: "success" | "error"` as a discriminant. A `switch` on that field narrows the entire object shape automatically. It is more reliable than property checks and is the pattern Redux Toolkit uses for action types. ## Examples ### typeof: narrowing a primitive union ```typescript type Shape = string | number; function doubleIt(shape: Shape) { if (typeof shape === "number") { return shape * 2; // number } return shape.toUpperCase(); // string } console.log(doubleIt(5)); // 10 console.log(doubleIt("hello")); // "HELLO" ``` `typeof` is the simplest guard. After the early return in the `number` branch, TypeScript infers the remaining code can only be `string`. ### in: polymorphic React component ```typescript interface ButtonProps { label: string; onClick: () => void; } interface LinkProps { href: string; children: string; } function InteractiveElement(props: ButtonProps | LinkProps) { if ("onClick" in props) { // narrowed to ButtonProps return <button onClick={props.onClick}>{props.label}</button>; } // narrowed to LinkProps return <a href={props.href}>{props.children}</a>; } ``` One function, two shapes, no casting. This pattern is common in component libraries like Mantine. ### User-defined guard: validating API responses ```typescript function isUser(obj: unknown): obj is { name: string; id: number } { return ( typeof obj === "object" && obj !== null && typeof (obj as any).name === "string" && typeof (obj as any).id === "number" ); } function processItem(item: unknown) { if (isUser(item)) { console.log(item.name.toUpperCase()); // narrowed } else { console.log("Not a valid user"); } } processItem({ name: "Alice", id: 1 }); // "ALICE" processItem({}); // "Not a valid user" ``` This is the pattern Zod uses internally. The guard returns `false` for anything that does not match, so failures are always handled safely without try/catch.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.