- •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 </when-to-activate>
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 </overview>
<workflow> ## 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 </workflow>
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 </examples>
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 </progressive-disclosure>
- •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 </constraints>
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 </patterns>
- •
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 </validation>
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.
</testing-guards>