Type Guards & Narrowing
Also Known As
type narrowing
user-defined type guards
is predicate
type predicates
TL;DR
Type guards are expressions that narrow a union type to a more specific type at runtime — telling TypeScript exactly which branch of a union you're in.
Explanation
TypeScript narrows types inside conditional branches automatically (typeof, instanceof, truthiness). Custom type guards use the is predicate: function isString(v: unknown): v is string { return typeof v === 'string'; }. Discriminated union narrowing works via a shared literal field (kind, type). The in operator narrows to types that have a given property. Exhaustiveness checks with never ensure all cases are handled. Common pattern: unknown input → series of type guards → fully typed value. Assertion functions (asserts v is T) throw instead of returning false.
Diagram
flowchart TD
INPUT["input: string | number | unknown"]
CHECK{Type guard check}
INPUT --> CHECK
CHECK -- typeof === string --> STR[Narrowed: string<br/>toUpperCase safe]
CHECK -- typeof === number --> NUM[Narrowed: number<br/>toFixed safe]
CHECK -- is predicate --> CUSTOM[Custom narrowing<br/>v is MyType]
CHECK -- discriminant field --> DISC[kind === circle<br/>radius available]
style STR fill:#238636,color:#fff
style NUM fill:#238636,color:#fff
Watch Out
⚠ A type guard with is predicate only narrows in the calling scope — if the function body itself has a bug (returns true unconditionally), TypeScript trusts it and the narrowing will be wrong at runtime.
Common Misconception
✗ Type guards only work with typeof and instanceof — user-defined is predicates and discriminated unions are far more powerful and the standard approach for domain types.
Why It Matters
Without type guards, working with union types and unknown inputs forces unsafe casts (as Type) that silently break at runtime — guards make narrowing explicit and compiler-verified.
Common Mistakes
- Using as Type (type assertion) instead of a proper type guard — assertions bypass the compiler and lie about the type.
- Forgetting the is predicate return type — without it, TypeScript doesn't narrow in the calling scope.
- Checking instanceof on plain objects — use in or a discriminant field instead.
- Not handling the never case in exhaustive switches — add a default branch that assigns to never to catch missing cases.
Avoid When
- Avoid writing type guards for every minor check — inline typeof/instanceof is fine for simple cases.
When To Use
- Processing unknown or any input from external sources (API responses, JSON.parse, user input).
- Working with union types that need different handling per variant.
- Building exhaustive switch statements over discriminated unions.
Code Examples
💡 Note
Shows unsafe casting replaced by a proper is predicate guard, plus discriminated union narrowing with an exhaustive never check.
✗ Vulnerable
function processInput(input: string | number) {
// Unsafe cast — no runtime check
const s = input as string;
console.log(s.toUpperCase()); // Crashes if input is number
}
✓ Fixed
// User-defined type guard
function isString(v: unknown): v is string {
return typeof v === 'string';
}
function processInput(input: string | number) {
if (isString(input)) {
console.log(input.toUpperCase()); // input is string here
} else {
console.log(input.toFixed(2)); // input is number here
}
}
// Discriminated union narrowing
type Shape = { kind: 'circle'; radius: number } | { kind: 'rect'; width: number; height: number };
function area(s: Shape): number {
switch (s.kind) {
case 'circle': return Math.PI * s.radius ** 2;
case 'rect': return s.width * s.height;
default: const _exhaustive: never = s; return _exhaustive;
}
}
Tags
🤝 Adopt this term
£79/year · your link shown here
Added
11 Apr 2026
Edited
19 Apr 2026
Views
19
🤖 AI Guestbook educational data only
|
|
Last 30 days
Agents 0
No pings yet today
No pings yesterday
Google 3
Perplexity 3
SEMrush 3
ChatGPT 1
Unknown AI 1
Ahrefs 1
Meta AI 1
Also referenced
How they use it
crawler 12
crawler_json 1
Related categories
⚡
DEV INTEL
Tools & Severity
🟠 High
⚙ Fix effort: Low
⚡ Quick Fix
Replace 'as Type' casts with 'v is Type' predicate functions. Use discriminant literal fields on union members for switch-based narrowing.
📦 Applies To
typescript 2.0
web
cli
🔗 Prerequisites
🔍 Detection Hints
as [A-Z][a-zA-Z]+(?!s\b)
Auto-detectable:
✓ Yes
typescript
eslint
⚠ Related Problems
🤖 AI Agent
Confidence: Medium
False Positives: Medium
✗ Manual fix
Fix: Medium
Context: Function