Custom Type Guards¶
A type guard is a function that tells TypeScript:
“If this function returns true, then the value is of this specific type.”
This allows TypeScript to narrow types based on your own logic, not just built‑in operators like typeof or instanceof.
Writing value is Type Functions¶
A custom type guard is any function that returns a type predicate:
function isString(value: unknown): value is string {
return typeof value === "string"
}
The magic is in the return type:
value is string
This tells TypeScript that inside an if (isString(value)) block, value should be treated as a string.
Example: Using the guard¶
function printUpper(value: unknown) {
if (isString(value)) {
console.log(value.toUpperCase()) // value: string
} else {
console.log("Not a string")
}
}
Without the guard, TypeScript would not allow value.toUpperCase().
Type Guards for Objects¶
Type guards become extremely useful when checking object shapes.
Example: Guarding a User object¶
type User = {
id: number
name: string
}
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value &&
typeof (value as any).id === "number" &&
typeof (value as any).name === "string"
)
}
Using the guard¶
const data: unknown = JSON.parse('{"id":1,"name":"Alice"}')
if (isUser(data)) {
console.log(data.name.toUpperCase()) // Safe
}
TypeScript now understands that data is a User inside the if.
Guarding External Data¶
External data is always unknown:
- API responses
- JSON files
- user input
- environment variables
- query parameters
- localStorage/sessionStorage
TypeScript cannot trust external data, so you must validate it.
Example: Guarding JSON from an API¶
type Product = {
id: number
title: string
price: number
}
function isProduct(value: unknown): value is Product {
return (
typeof value === "object" &&
value !== null &&
typeof (value as any).id === "number" &&
typeof (value as any).title === "string" &&
typeof (value as any).price === "number"
)
}
Using it with fetch¶
async function loadProduct() {
const res = await fetch("/api/product")
const data: unknown = await res.json()
if (!isProduct(data)) {
throw new Error("Invalid product data")
}
console.log(data.title) // Safe
}
This pattern prevents entire classes of runtime bugs.
Type Guards for Discriminated Unions¶
Custom guards can also narrow discriminated unions.
Example: Result type¶
type Success = { status: "success"; data: string }
type Failure = { status: "error"; error: Error }
type Result = Success | Failure
Custom guard¶
function isSuccess(result: Result): result is Success {
return result.status === "success"
}
Using it¶
function handle(result: Result) {
if (isSuccess(result)) {
console.log(result.data)
} else {
console.error(result.error.message)
}
}
This is cleaner and more reusable than repeating the same checks everywhere.
Type Guards for Arrays¶
You can validate arrays too.
Example: Guarding an array of numbers¶
function isNumberArray(value: unknown): value is number[] {
return Array.isArray(value) && value.every(item => typeof item === "number")
}
Usage¶
const data: unknown = [1, 2, 3]
if (isNumberArray(data)) {
const sum = data.reduce((a, b) => a + b, 0)
}
Putting It All Together¶
Here’s a realistic example combining multiple guard techniques:
type User = {
id: number
name: string
roles: string[]
}
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
typeof (value as any).id === "number" &&
typeof (value as any).name === "string" &&
Array.isArray((value as any).roles) &&
(value as any).roles.every((r: unknown) => typeof r === "string")
)
}
async function loadUser() {
const res = await fetch("/api/user")
const data: unknown = await res.json()
if (!isUser(data)) {
throw new Error("Invalid user data")
}
console.log(data.roles.join(", "))
}
This is the exact pattern used in production systems to validate external data safely.
Summary¶
In this lesson, you learned how to create and use custom type guards:
1. Writing value is Type functions¶
- Use type predicates to teach TypeScript new narrowing rules
- Works for primitives, objects, arrays, and unions
2. Guarding external data¶
- All external data should be treated as
unknown - Validate shape and types before using
- Prevents runtime crashes and invalid states
Custom type guards are one of the most powerful tools in TypeScript—they let you bring real‑world data safely into your type system.