What is a type guard function?
What a Type Guard Function is
A type guard function is a special function that returns a boolean and tells TypeScript that if it returned
true, then its argument has a specific type.
Syntax:
function isSomething(value: unknown): value is SomeType {
// check logic
}The key part is the return type:
value is SomeType -> a type predicate.
Example 1. A basic custom type guard
function isString(value: unknown): value is string {
return typeof value === "string";
}
function print(value: unknown) {
if (isString(value)) {
// here value: string
console.log(value.toUpperCase());
} else {
// value is not a string
console.log("Not a string");
}
}Thanks to value is string,
TypeScript understands that inside if (isString(value))
the variable's type has narrowed to string.
Why Type Guard Functions are needed
- To extend TypeScript's type system - teaching it to understand your checks.
- To avoid forced casts (
as). - To safely work with
unknown,any, or union types. - To narrow types inside logical conditions, filters, and higher-order functions.
Example 2. Checking an array
function isArray(value: unknown): value is any[] {
return Array.isArray(value);
}
function process(value: unknown) {
if (isArray(value)) {
// value: any[]
console.log(value.length);
} else {
console.log("Not an array");
}
}How this works under the hood
A regular function returns boolean:
function check(value: unknown): booleanTypeScript cannot infer a type from this.
But a function with a type predicate (value is Type) tells the compiler:
"If I returned true, treat the variable as now having type
Type".
Example 3. With a union type
type Dog = { kind: "dog"; bark: () => void };
type Cat = { kind: "cat"; meow: () => void };
function isDog(animal: Dog | Cat): animal is Dog {
return animal.kind === "dog";
}
function makeSound(animal: Dog | Cat) {
if (isDog(animal)) {
animal.bark(); // animal: Dog
} else {
animal.meow(); // animal: Cat
}
}This is a typical example of type guard + narrowing:
the isDog function narrows the union type Dog | Cat to a specific subtype.
Where it's often used
| Scenario | Example |
|---|---|
| Checking an argument's type | isString(x) |
| Checking for null/undefined | isNonNull(x) |
| Checking a discriminant | isError(response) |
| An instanceof check | isDate(obj) |
| Filtering an array | arr.filter(isString) |
Example 4. Filtering an array with a Type Guard
function isString(value: unknown): value is string {
return typeof value === "string";
}
const mixed = [1, "a", 2, "b", true];
const strings = mixed.filter(isString);
// strings: string[]TypeScript knows that only strings remain after the filter.
Example 5. Checking complex structures
type User = { id: number; name: string };
function isUser(obj: any): obj is User {
return (
typeof obj === "object" &&
obj !== null &&
typeof obj.id === "number" &&
typeof obj.name === "string"
);
}
const data: unknown = JSON.parse('{"id":1,"name":"Tom"}');
if (isUser(data)) {
// data: User
console.log(data.name.toUpperCase());
}Without a type guard you would have had to write:
const user = data as User; // unsafe, may fail at runtimeExample 6. Checking whether a key exists
function hasProperty<T extends object, K extends PropertyKey>(
obj: T,
key: K
): obj is T & Record<K, unknown> {
return key in obj;
}
const person = { name: "Alex" };
if (hasProperty(person, "name")) {
// person: { name: string } & Record<"name", unknown>
console.log(person.name);
}Example 7. Composing several type guards
function isDefined<T>(value: T | undefined | null): value is T {
return value != null;
}
function isPositive(n: number): boolean {
return n > 0;
}
const arr = [1, null, 2, undefined, -5, 3];
const positive = arr.filter(isDefined).filter(isPositive);
// positive: number[]After isDefined, TypeScript knows that the array no longer contains null or undefined.
The difference between a Type Guard Function and a regular function
| Regular function | Type Guard Function |
|---|---|
Returns boolean | Returns value is Type |
| Does not change the variable's type | Narrows the variable's type |
| Does not affect the compiler | Changes how TS perceives the type |
| Used as a logical check | Used as proof of type |
Example: how TS "thinks"
if (isUser(obj)) {
// TypeScript "believed" the isUser check
// obj: User
} else {
// obj: unknown
}TypeScript does not execute the function,
it simply trusts the value is Type annotation -
this is the developer's promise that the function's logic is indeed correct.
Important notes
- TypeScript does not verify that the check inside is correct - that is the developer's responsibility.
function isNumber(x: any): x is number {
return typeof x === "string"; // TS will believe this, even though it's wrong
}- Type guards work only with a function parameter, not with arbitrary variables.
- Generics can be used to build universal guards.
Summary
| Term | Definition |
|---|---|
| Type Guard Function | A function that returns value is Type and tells TS that the value has the specified type |
| Purpose | Narrowing types and safely working with unknown, any, and union types |
| Main feature | Affects type analysis, not just execution logic |
| Typical syntax | function isUser(obj: any): obj is User { ... } |
| Main advantage | Safety, autocomplete, and no as |
Just remember:
A Type Guard Function is a "guard" that tells TypeScript: "I checked it, you can trust me - this is exactly this type".
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.