typeof Guards
typeof Guards
The typeof operator checks the type of a value at runtime, allowing TypeScript to narrow the type.
Basic typeof
function process(value: string | number | boolean) {
if (typeof value === "string") {
// TypeScript knows value is string
return value.toUpperCase();
} else if (typeof value === "number") {
// TypeScript knows value is number
return value.toFixed(2);
} else {
// TypeScript knows value is boolean
return value ? "yes" : "no";
}
}
typeof with undefined
function greet(name: string | undefined) {
if (typeof name === "string") {
return `Hello, ${name}`;
}
return "Hello, stranger";
}
typeof in Callbacks
function handleEvent(event: KeyboardEvent | MouseEvent) {
if (typeof event.key === "string") {
// KeyboardEvent
console.log(event.key);
} else {
// MouseEvent
console.log(event.clientX);
}
}
Limitations
typeof only works with primitives:
function process(value: string | object) {
if (typeof value === "object") {
// Can't distinguish between different object types
// value is still string | object
}
}
instanceof Guards
instanceof Guards
The instanceof operator checks if an object is an instance of a specific class.
Basic instanceof
class HttpError {
constructor(public statusCode: number, public message: string) {}
}
class NetworkError {
constructor(public message: string) {}
}
function handleError(error: HttpError | NetworkError) {
if (error instanceof HttpError) {
console.log(`HTTP ${error.statusCode}: ${error.message}`);
} else {
console.log(`Network: ${error.message}`);
}
}
instanceof with Built-in Classes
function processDate(value: Date | string) {
if (value instanceof Date) {
return value.getFullYear();
}
return new Date(value).getFullYear();
}
instanceof with Error Types
function catchBlock(error: unknown) {
if (error instanceof TypeError) {
console.log("Type error:", error.message);
} else if (error instanceof RangeError) {
console.log("Range error:", error.message);
} else if (error instanceof Error) {
console.log("Generic error:", error.message);
}
}
instanceof vs typeof
// Use typeof for primitives
function isString(value: unknown): value is string {
return typeof value === "string";
}
// Use instanceof for classes
function isError(value: unknown): value is Error {
return value instanceof Error;
}
Discriminated Unions
Discriminated Unions
Discriminated unions use a common property to narrow types in switch statements.
Pattern
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number }
| { kind: "triangle"; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
// TypeScript knows shape is circle
return Math.PI * shape.radius ** 2;
case "rectangle":
// TypeScript knows shape is rectangle
return shape.width * shape.height;
case "triangle":
// TypeScript knows shape is triangle
return (shape.base * shape.height) / 2;
}
}
Exhaustive Switch
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
case "triangle":
return (shape.base * shape.height) / 2;
default:
const _exhaustive: never = shape;
return _exhaustive;
}
}
Real-World Example
type Action =
| { type: "INCREMENT"; amount: number }
| { type: "DECREMENT"; amount: number }
| { type: "RESET" };
function reducer(state: number, action: Action): number {
switch (action.type) {
case "INCREMENT":
return state + action.amount;
case "DECREMENT":
return state - action.amount;
case "RESET":
return 0;
}
}
Practice Problems
Create a reusable React component implementing Type Narrowing. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Type Narrowing using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Type Narrowing for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What does typeof check?
2. When should you use instanceof?
3. What is a discriminated union?
4. What does the never type do in a switch?
Flashcards
Question
What does typeof guard check?
Click to reveal answer
Answer
Primitive types: string, number, boolean, undefined, etc.
Question
When do you use instanceof?
Click to reveal answer
Answer
To check if an object is an instance of a class
Question
What makes a discriminated union?
Click to reveal answer
Answer
A common literal property that distinguishes between types
Question
How do you ensure exhaustive type checking?
Click to reveal answer
Answer
Use the never type in a default switch case
Question
What is Type Narrowing?
Click to reveal answer
Answer
Type Narrowing is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.typeof guards narrow primitive types
- 2.instanceof guards narrow class instances
- 3.Discriminated unions use a common property for narrowing
- 4.Exhaustive checking ensures all cases are handled
- 5.Type narrowing happens automatically in control flow
Interview Tips
- •Explain when to use typeof vs instanceof
- •Show how discriminated unions simplify complex logic
- •Demonstrate exhaustive checking with never
Cheat Sheet
Cheat Sheet
typeof
if (typeof x === "string") {
// x is string
}
instanceof
if (x instanceof Error) {
// x is Error
}
Discriminated Union
type Shape =
| { kind: "circle"; r: number }
| { kind: "rect"; w: number; h: number };
switch (shape.kind) {
case "circle": //...
case "rect": //...
}
Exhaustive Check
default:
const _: never = shape;
return _;