In the previous tutorial, we learned about modules and namespaces. Now let’s learn about utility types — built-in types that transform other types.
TypeScript includes many utility types out of the box. They save you from writing complex type logic yourself. By the end of this tutorial, you will know when and how to use each one.
Why Utility Types?
Imagine you have a User type:
interface User {
id: number;
name: string;
email: string;
age: number;
}
Now you need a function that updates a user. But you want to allow partial updates — only some fields, not all. Without utility types, you would need to create a new type manually:
// Tedious — you repeat every field with ?
interface UserUpdate {
id?: number;
name?: string;
email?: string;
age?: number;
}
With utility types, one line does the job:
type UserUpdate = Partial<User>;
Partial
Partial<T> makes all properties optional:
interface User {
id: number;
name: string;
email: string;
}
type PartialUser = Partial<User>;
// Result:
// {
// id?: number;
// name?: string;
// email?: string;
// }
Real-World Use: Update Functions
function updateUser(id: number, updates: Partial<User>): User {
const existing = getUserById(id);
return { ...existing, ...updates };
}
// Only update the name — other fields stay the same
updateUser(1, { name: "Sam" });
// Update multiple fields
updateUser(1, { name: "Sam", email: "sam@example.com" });
Required
Required<T> is the opposite of Partial. It makes all properties required:
interface Config {
host?: string;
port?: number;
debug?: boolean;
}
type FullConfig = Required<Config>;
// Result:
// {
// host: string;
// port: number;
// debug: boolean;
// }
This is useful when you have a config with defaults and want to ensure all values are set after merging:
const defaults: Required<Config> = {
host: "localhost",
port: 3000,
debug: false,
};
function createConfig(overrides: Config): Required<Config> {
return { ...defaults, ...overrides };
}
Readonly
Readonly<T> makes all properties readonly:
interface User {
id: number;
name: string;
}
type ReadonlyUser = Readonly<User>;
// Result:
// {
// readonly id: number;
// readonly name: string;
// }
const user: ReadonlyUser = { id: 1, name: "Alex" };
// user.name = "Sam"; // Error: Cannot assign to 'name' because it is a read-only property
Use this for data that should never change after creation, like database records or API responses.
Pick<T, K>
Pick<T, K> creates a new type with only the specified properties:
interface User {
id: number;
name: string;
email: string;
age: number;
createdAt: Date;
}
type UserPreview = Pick<User, "id" | "name">;
// Result:
// {
// id: number;
// name: string;
// }
Real-World Use: API Responses
// Full user for internal use
interface User {
id: number;
name: string;
email: string;
passwordHash: string;
createdAt: Date;
}
// Public user for API responses — no sensitive fields
type PublicUser = Pick<User, "id" | "name" | "email">;
function toPublicUser(user: User): PublicUser {
return {
id: user.id,
name: user.name,
email: user.email,
};
}
Omit<T, K>
Omit<T, K> is the opposite of Pick. It creates a type with all properties except the specified ones:
interface User {
id: number;
name: string;
email: string;
passwordHash: string;
}
type SafeUser = Omit<User, "passwordHash">;
// Result:
// {
// id: number;
// name: string;
// email: string;
// }
Real-World Use: Create vs Read Types
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
// When creating a user, id and createdAt are generated by the server
type CreateUser = Omit<User, "id" | "createdAt">;
function createUser(data: CreateUser): User {
return {
...data,
id: generateId(),
createdAt: new Date(),
};
}
createUser({ name: "Alex", email: "alex@example.com" });
Record<K, T>
Record<K, T> creates an object type where all keys have the same value type:
type Role = "admin" | "editor" | "viewer";
type RolePermissions = Record<Role, string[]>;
const permissions: RolePermissions = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"],
};
If you forget a key, TypeScript shows an error:
// Error: Property 'viewer' is missing
const bad: RolePermissions = {
admin: ["read"],
editor: ["read"],
};
Real-World Use: Lookup Maps
type StatusCode = 200 | 404 | 500;
const statusMessages: Record<StatusCode, string> = {
200: "OK",
404: "Not Found",
500: "Internal Server Error",
};
Exclude<T, U> and Extract<T, U>
These work on union types, not object types.
Exclude<T, U>
Removes types from a union:
type Status = "active" | "inactive" | "banned" | "deleted";
type ActiveStatus = Exclude<Status, "banned" | "deleted">;
// Result: "active" | "inactive"
Extract<T, U>
Keeps only types that match:
type Status = "active" | "inactive" | "banned" | "deleted";
type DangerousStatus = Extract<Status, "banned" | "deleted">;
// Result: "banned" | "deleted"
Real-World Use: Filtering Event Types
type Event =
| { type: "click"; x: number; y: number }
| { type: "keypress"; key: string }
| { type: "scroll"; offset: number };
// Extract only mouse events
type MouseEvent = Extract<Event, { type: "click" }>;
// Result: { type: "click"; x: number; y: number }
NonNullable
Removes null and undefined from a type:
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// Result: string
Useful for cleaning up types from optional values:
interface User {
name: string;
nickname?: string;
}
type Nickname = NonNullable<User["nickname"]>;
// Result: string (removed undefined)
ReturnType and Parameters
ReturnType
Gets the return type of a function:
function getUser() {
return { id: 1, name: "Alex", email: "alex@example.com" };
}
type User = ReturnType<typeof getUser>;
// Result: { id: number; name: string; email: string }
This is useful when you don’t want to define a separate type for the return value.
Parameters
Gets the parameter types as a tuple:
function createUser(name: string, age: number, email: string): void {
// ...
}
type CreateUserParams = Parameters<typeof createUser>;
// Result: [string, number, string]
// You can extract individual parameters
type FirstParam = CreateUserParams[0]; // string
Awaited
Unwraps Promise types:
type PromiseString = Promise<string>;
type Result = Awaited<PromiseString>;
// Result: string
// Works with nested promises too
type Nested = Promise<Promise<number>>;
type FinalResult = Awaited<Nested>;
// Result: number
Real-World Use: Extracting API Response Types
async function fetchUsers(): Promise<User[]> {
const res = await fetch("/api/users");
return res.json();
}
type Users = Awaited<ReturnType<typeof fetchUsers>>;
// Result: User[]
Combining Utility Types
The real power comes from combining multiple utility types:
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Partial update without sensitive fields
type UserUpdate = Partial<Omit<User, "id" | "password" | "createdAt">>;
// Result:
// {
// name?: string;
// email?: string;
// }
// Readonly public user
type PublicUser = Readonly<Pick<User, "id" | "name" | "email">>;
// Result:
// {
// readonly id: number;
// readonly name: string;
// readonly email: string;
// }
Quick Reference Table
| Utility Type | What It Does | Example |
|---|---|---|
Partial<T> | All properties optional | Partial<User> |
Required<T> | All properties required | Required<Config> |
Readonly<T> | All properties readonly | Readonly<User> |
Pick<T, K> | Keep specific properties | Pick<User, "id" | "name"> |
Omit<T, K> | Remove specific properties | Omit<User, "password"> |
Record<K, T> | Object with typed keys and values | Record<string, number> |
Exclude<T, U> | Remove types from union | Exclude<Status, "banned"> |
Extract<T, U> | Keep matching types from union | Extract<Event, { type: "click" }> |
NonNullable<T> | Remove null and undefined | NonNullable<string | null> |
ReturnType<T> | Get function return type | ReturnType<typeof fn> |
Parameters<T> | Get function parameter types | Parameters<typeof fn> |
Awaited<T> | Unwrap Promise type | Awaited<Promise<string>> |
What’s Next?
In the next tutorial, we will learn about mapped types and conditional types — how to build your own utility types and transform types programmatically.