9.5 KiB
name, description, allowed-tools, version
| name | description | allowed-tools | version |
|---|---|---|---|
| using-type-guards | 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. | Read, Write, Edit, Glob, Grep, Bash, Task, TodoWrite | 1.0.0 |
- Working with
unknownoranytypes - 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
Three Categories:
- Built-in Guards:
typeof,instanceof,in,Array.isArray() - Type Predicates: Custom functions returning
value is Type - Assertion Functions: Functions that throw if type check fails
Key Pattern: Runtime check → Type narrowing → Safe access
## Type Guard Selection FlowStep 1: Identify the Type Challenge
What do you need to narrow?
- Primitive types → Use
typeof - Class instances → Use
instanceof - Object shapes → Use
inor 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)
typeof value === "string"
value instanceof Error
"property" in value
Array.isArray(value)
Custom Type Predicates (object shapes, complex logic)
function isUser(value: unknown): value is User {
return typeof value === "object" &&
value !== null &&
"id" in value;
}
Validation Libraries (nested structures, multiple fields)
const UserSchema = z.object({
id: z.string(),
email: z.string().email()
});
Step 3: Implement Guard
- Check for
nullandundefinedfirst - Check base type (object, array, primitive)
- Check structure (properties exist)
- Check property types
- Return type predicate or throw
typeof Guard
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 matchesnull- check fornullfirst!)
instanceof Guard
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
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
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
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
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
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
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 Typereturn type - Good for precondition checks
Local References (in references/ directory):
advanced-patterns.md- Optional properties, arrays, tuples, records, enumsnested-validation.md- Complex nested object validation patternstesting-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
- Check for
nullandundefinedbefore property access - Check object base type before using
inoperator - 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
isTypeorassertIsType
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
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
-
Null Safety:
- Checks for
nullbefore usinginor property access - Checks for
undefinedfor optional values
- Checks for
-
Complete Validation:
- Validates all required properties exist
- Validates property types, not just existence
- Validates nested objects recursively
-
Type Predicate:
- Returns
value is Typefor reusable guards - Or uses
asserts value is Typefor assertion functions
- Returns
-
Edge Cases:
- Handles arrays correctly
- Handles empty objects
- Handles inherited properties if relevant
-
Composition:
- Complex guards built from simpler guards
- Guards are unit testable
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.