--- name: using-type-guards description: Teaches how to write custom type guards with type predicates and use built-in type narrowing in TypeScript. Use when working with unknown types, union types, validating external data, or implementing type-safe runtime checks. allowed-tools: Read, Write, Edit, Glob, Grep, Bash, Task, TodoWrite version: 1.0.0 --- This skill teaches how to safely narrow types at runtime using type guards, enabling type-safe handling of dynamic data and union types. Critical for validating external data and preventing runtime errors. This skill activates when: - Working with `unknown` or `any` types - Handling union types that need discrimination - Validating external data (APIs, user input, JSON) - Implementing runtime type checks - User mentions type guards, type narrowing, type predicates, or runtime validation Type guards are runtime checks that inform TypeScript's type system about the actual type of a value. They bridge the gap between compile-time types and runtime values. **Three Categories**: 1. **Built-in Guards**: `typeof`, `instanceof`, `in`, `Array.isArray()` 2. **Type Predicates**: Custom functions returning `value is Type` 3. **Assertion Functions**: Functions that throw if type check fails **Key Pattern**: Runtime check → Type narrowing → Safe access ## Type Guard Selection Flow **Step 1: Identify the Type Challenge** What do you need to narrow? - Primitive types → Use `typeof` - Class instances → Use `instanceof` - Object shapes → Use `in` or custom type predicate - Array types → Use `Array.isArray()` + element checks - Complex structures → Use validation library (Zod, io-ts) **Step 2: Choose Guard Strategy** **Built-in Guards** (primitives, classes, simple checks) ```typescript typeof value === "string" value instanceof Error "property" in value Array.isArray(value) ``` **Custom Type Predicates** (object shapes, complex logic) ```typescript function isUser(value: unknown): value is User { return typeof value === "object" && value !== null && "id" in value; } ``` **Validation Libraries** (nested structures, multiple fields) ```typescript const UserSchema = z.object({ id: z.string(), email: z.string().email() }); ``` **Step 3: Implement Guard** 1. Check for `null` and `undefined` first 2. Check base type (object, array, primitive) 3. Check structure (properties exist) 4. Check property types 5. Return type predicate or throw ## Example 1: Built-in Type Guards ### typeof Guard ```typescript function processValue(value: unknown): string { if (typeof value === "string") { return value.toUpperCase(); } if (typeof value === "number") { return value.toFixed(2); } if (typeof value === "boolean") { return value ? "yes" : "no"; } throw new Error("Unsupported type"); } ``` **typeof Guards Work For**: - `"string"`, `"number"`, `"boolean"`, `"symbol"`, `"undefined"` - `"function"`, `"bigint"` - `"object"` (but also matches `null` - check for `null` first!) ### instanceof Guard ```typescript function handleError(error: unknown): string { if (error instanceof Error) { return error.message; } if (error instanceof CustomError) { return `Custom error: ${error.code}`; } return String(error); } ``` **instanceof Works For**: - Class instances - Built-in classes (Error, Date, Map, Set, etc.) - DOM elements (HTMLElement, etc.) ### in Guard ```typescript type Circle = { kind: "circle"; radius: number }; type Square = { kind: "square"; sideLength: number }; type Shape = Circle | Square; function getArea(shape: Shape): number { if ("radius" in shape) { return Math.PI * shape.radius ** 2; } else { return shape.sideLength ** 2; } } ``` **in Guard Best For**: - Discriminating union types - Checking optional properties - Object shape validation --- ## Example 2: Custom Type Predicates ### Basic Type Predicate ```typescript interface User { id: string; name: string; email: string; } function isUser(value: unknown): value is User { return ( typeof value === "object" && value !== null && "id" in value && "name" in value && "email" in value && typeof (value as User).id === "string" && typeof (value as User).name === "string" && typeof (value as User).email === "string" ); } function processUser(data: unknown): void { if (isUser(data)) { console.log(data.name); } else { throw new Error("Invalid user data"); } } ``` ### Array Type Predicate ```typescript function isStringArray(value: unknown): value is string[] { return Array.isArray(value) && value.every(item => typeof item === "string"); } function processNames(data: unknown): string { if (isStringArray(data)) { return data.join(", "); } throw new Error("Expected array of strings"); } ``` ### Nested Type Predicate For complex nested structures, compose guards from simpler guards. See `references/nested-validation.md` for detailed examples. --- ## Example 3: Discriminated Unions ### Using Literal Type Discrimination ```typescript type Success = { status: "success"; data: string; }; type Failure = { status: "error"; error: string; }; type Result = Success | Failure; function handleResult(result: Result): void { if (result.status === "success") { console.log("Data:", result.data); } else { console.error("Error:", result.error); } } ``` ### Runtime Validation of Discriminated Union ```typescript function isSuccess(value: unknown): value is Success { return ( typeof value === "object" && value !== null && "status" in value && value.status === "success" && "data" in value && typeof (value as Success).data === "string" ); } function isFailure(value: unknown): value is Failure { return ( typeof value === "object" && value !== null && "status" in value && value.status === "error" && "error" in value && typeof (value as Failure).error === "string" ); } function isResult(value: unknown): value is Result { return isSuccess(value) || isFailure(value); } ``` --- ## Example 4: Assertion Functions ```typescript function assertIsString(value: unknown): asserts value is string { if (typeof value !== "string") { throw new Error(`Expected string, got ${typeof value}`); } } function assertIsUser(value: unknown): asserts value is User { if (!isUser(value)) { throw new Error("Invalid user data"); } } function processData(data: unknown): void { assertIsUser(data); console.log(data.name); } ``` **Assertion Functions**: - Throw error if check fails - Narrow type for remainder of scope - Use `asserts value is Type` return type - Good for precondition checks ## Reference Files **Local References** (in `references/` directory): - `advanced-patterns.md` - Optional properties, arrays, tuples, records, enums - `nested-validation.md` - Complex nested object validation patterns - `testing-guide.md` - Complete unit testing guide with examples **Related Skills**: - **Runtime Validation Libraries**: Use the using-runtime-checks skill for Zod/io-ts patterns - **Unknown Type Handling**: Use the avoiding-any-types skill for when to use type guards - **Error Type Guards**: Error-specific type guard patterns can be found in error handling documentation **MUST:** - Check for `null` and `undefined` before property access - Check object base type before using `in` operator - Use type predicates (`value is Type`) for reusable guards - Validate all required properties exist - Validate property types, not just existence **SHOULD:** - Compose simple guards into complex guards - Use discriminated unions for known variants - Prefer built-in guards over custom when possible - Name guard functions `isType` or `assertIsType` **NEVER:** - Access properties before type guard - Forget to check for `null` (typeof null === "object"!) - Use type assertions instead of type guards for external data - Assume property exists after checking with `in` - Skip validating nested object types ## Common Patterns **Basic Patterns** (covered in examples above): - Built-in guards: typeof, instanceof, in, Array.isArray() - Custom type predicates for object shapes - Discriminated union narrowing - Assertion functions **Advanced Patterns** (see `references/advanced-patterns.md`): - Optional property validation - Array element guards with generics - Tuple guards - Record guards - Enum guards ## Type Guard Quality Checklist 1. **Null Safety**: - [ ] Checks for `null` before using `in` or property access - [ ] Checks for `undefined` for optional values 2. **Complete Validation**: - [ ] Validates all required properties exist - [ ] Validates property types, not just existence - [ ] Validates nested objects recursively 3. **Type Predicate**: - [ ] Returns `value is Type` for reusable guards - [ ] Or uses `asserts value is Type` for assertion functions 4. **Edge Cases**: - [ ] Handles arrays correctly - [ ] Handles empty objects - [ ] Handles inherited properties if relevant 5. **Composition**: - [ ] Complex guards built from simpler guards - [ ] Guards are unit testable ## Unit Testing Type Guards Test all edge cases: valid inputs, missing properties, wrong types, null, undefined, and non-objects. See `references/testing-guide.md` for complete testing strategies and examples.