Module 16: Utility Types
TypeScript provides powerful built-in utility types for common type transformations. Master these to write more elegant and maintainable code.
1. Partial<T>
Makes all properties optional.
interface User {
id: number;
name: string;
email: string;
age: number;
}
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number; }
function updateUser(id: number, updates: Partial<User>): User {
const currentUser = getUser(id);
return { ...currentUser, ...updates };
}
updateUser(1, { name: "Alice" }); // ✅ Only name
updateUser(1, { email: "alice@example.com", age: 26 }); // ✅ Multiple fields
2. Required<T>
Makes all properties required.
interface Config {
host?: string;
port?: number;
ssl?: boolean;
}
type RequiredConfig = Required<Config>;
// { host: string; port: number; ssl: boolean; }
function initialize(config: Required<Config>): void {
// All properties are guaranteed to exist
console.log(`Connecting to ${config.host}:${config.port}`);
}
3. Readonly<T>
Makes all properties readonly.
interface Mutable {
name: string;
age: number;
}
type Immutable = Readonly<Mutable>;
// { readonly name: string; readonly age: number; }
const user: Immutable = { name: "Alice", age: 25 };
// user.name = "Bob"; // ❌ Error: Cannot assign to 'name'
4. Record<K, T>
Create an object type with specific keys and value type.
type Role = "admin" | "user" | "guest";
type Permissions = Record<Role, string[]>;
const permissions: Permissions = {
admin: ["read", "write", "delete"],
user: ["read", "write"],
guest: ["read"]
};
// Dynamic keys
type UserRecord = Record<string, User>;
const users: UserRecord = {
"alice": { id: 1, name: "Alice", email: "alice@example.com" },
"bob": { id: 2, name: "Bob", email: "bob@example.com" }
};
5. Pick<T, K>
Select specific properties from a type.
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
type UserPreview = Pick<User, "id" | "name" | "email">;
// { id: number; name: string; email: string; }
type Credentials = Pick<User, "email" | "password">;
// { email: string; password: string; }
function displayUser(user: UserPreview): void {
console.log(`${user.name} (${user.email})`);
}
6. Omit<T, K>
Exclude specific properties from a type.
interface User {
id: number;
name: string;
email: string;
password: string;
}
type UserWithoutPassword = Omit<User, "password">;
// { id: number; name: string; email: string; }
type CreateUserInput = Omit<User, "id">;
// { name: string; email: string; password: string; }
function createUser(data: CreateUserInput): User {
return { id: generateId(), ...data };
}
7. Exclude<T, U>
Remove types from a union.
type T0 = Exclude<"a" | "b" | "c", "a">;
// "b" | "c"
type T1 = Exclude<string | number | boolean, boolean>;
// string | number
type Primitive = string | number | boolean | null | undefined;
type NonNullablePrimitive = Exclude<Primitive, null | undefined>;
// string | number | boolean
8. Extract<T, U>
Extract types from a union that are assignable to another type.
type T0 = Extract<"a" | "b" | "c", "a" | "f">;
// "a"
type T1 = Extract<string | number | boolean, number | boolean>;
// number | boolean
type Shape = "circle" | "square" | "triangle";
type Polygon = "square" | "triangle" | "pentagon";
type CommonShapes = Extract<Shape, Polygon>;
// "square" | "triangle"
9. NonNullable<T>
Remove null and undefined from a type.
type T0 = NonNullable<string | number | null | undefined>;
// string | number
type T1 = NonNullable<string[] | null | undefined>;
// string[]
function processValue(value: string | null | undefined): void {
const nonNull: NonNullable<typeof value> = value ?? "default";
console.log(nonNull.toUpperCase());
}
10. ReturnType<T>
Extract the return type of a function.
function getUser() {
return { id: 1, name: "Alice", email: "alice@example.com" };
}
type User = ReturnType<typeof getUser>;
// { id: number; name: string; email: string; }
function createAsync<T>(value: T): Promise<T> {
return Promise.resolve(value);
}
type AsyncString = ReturnType<typeof createAsync<string>>;
// Promise<string>
11. Parameters<T>
Extract parameter types of a function.
function createUser(name: string, age: number, email: string) {
return { name, age, email };
}
type CreateUserParams = Parameters<typeof createUser>;
// [string, number, string]
function logFunction(fn: (...args: Parameters<typeof createUser>) => any) {
console.log("Function called");
}
12. ConstructorParameters<T>
Extract constructor parameter types.
class User {
constructor(public name: string, public age: number) {}
}
type UserConstructorParams = ConstructorParameters<typeof User>;
// [string, number]
function createUser(...args: ConstructorParameters<typeof User>): User {
return new User(...args);
}
13. InstanceType<T>
Extract instance type of a constructor.
class User {
constructor(public name: string, public age: number) {}
}
type UserInstance = InstanceType<typeof User>;
// User
function processUser(user: InstanceType<typeof User>): void {
console.log(user.name);
}
14. ThisParameterType<T>
Extract the type of 'this' parameter.
function greet(this: User, greeting: string) {
return `${greeting}, ${this.name}`;
}
type ThisType = ThisParameterType<typeof greet>;
// User
type NoThisType = ThisParameterType<() => void>;
// unknown
15. OmitThisParameter<T>
Remove 'this' parameter from function type.
function greet(this: User, greeting: string): string {
return `${greeting}, ${this.name}`;
}
type GreetFunction = OmitThisParameter<typeof greet>;
// (greeting: string) => string
const greetFunc: GreetFunction = (greeting) => `${greeting}!`;
16. Awaited<T>
Unwrap Promise types.
type T0 = Awaited<Promise<string>>;
// string
type T1 = Awaited<Promise<Promise<number>>>;
// number
type T2 = Awaited<boolean | Promise<string>>;
// boolean | string
async function getData(): Promise<{ id: number; name: string }> {
return { id: 1, name: "Alice" };
}
type Data = Awaited<ReturnType<typeof getData>>;
// { id: number; name: string; }
17. Combining Utility Types
interface User {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
createdAt: Date;
}
// Partial user without password
type UpdateUserInput = Partial<Omit<User, "id" | "password">>;
// Required user preview
type UserDisplay = Required<Pick<User, "id" | "name" | "email">>;
// Readonly user without sensitive data
type SafeUser = Readonly<Omit<User, "password">>;
18. Custom Utility Types
// DeepPartial: Make all nested properties optional
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface Config {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
}
const update: DeepPartial<Config> = {
database: {
credentials: {
password: "newpass"
}
}
};
DeepReadonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
Mutable (Opposite of Readonly)
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface ReadonlyUser {
readonly id: number;
readonly name: string;
}
type MutableUser = Mutable<ReadonlyUser>;
// { id: number; name: string; }
19. Real-World Example: API Types
interface User {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
createdAt: Date;
updatedAt: Date;
}
// API Response (without sensitive data)
type UserResponse = Omit<User, "password">;
// Create request (no id, timestamps)
type CreateUserRequest = Omit<User, "id" | "createdAt" | "updatedAt">;
// Update request (partial, no id, timestamps)
type UpdateUserRequest = Partial<Omit<User, "id" | "createdAt" | "updatedAt" | "password">>;
// Database model (all required)
type UserModel = Required<User>;
// Public profile (minimal info)
type PublicProfile = Pick<User, "id" | "name">;
// Admin view (everything except password)
type AdminUserView = Readonly<Omit<User, "password">>;
20. Form State Management
interface FormState<T> {
values: T;
errors: Partial<Record<keyof T, string>>;
touched: Partial<Record<keyof T, boolean>>;
isSubmitting: boolean;
}
interface LoginForm {
email: string;
password: string;
rememberMe: boolean;
}
type LoginFormState = FormState<LoginForm>;
const formState: LoginFormState = {
values: { email: "", password: "", rememberMe: false },
errors: { email: "Required" },
touched: { email: true },
isSubmitting: false
};
Key Takeaways
✅ Partial/Required toggle optional properties
✅ Pick/Omit select or exclude properties
✅ Record creates object types with specific keys
✅ ReturnType/Parameters extract function types
✅ Awaited unwraps Promise types
✅ Combine utilities for complex transformations
✅ Create custom utilities for specific needs
Practice Exercises
Exercise 1: CRUD Types
interface Product {
id: number;
name: string;
price: number;
description: string;
stock: number;
createdAt: Date;
}
type CreateProduct = Omit<Product, "id" | "createdAt">;
type UpdateProduct = Partial<Pick<Product, "name" | "price" | "description" | "stock">>;
type ProductPreview = Pick<Product, "id" | "name" | "price">;
Exercise 2: Event Handler Types
Extract proper types for event handlers.
Exercise 3: API Response Wrapper
type APIResponse<T> = {
data: T;
error: string | null;
loading: boolean;
};
type UserAPIResponse = APIResponse<User>;
Next Steps
In Module 17, we'll explore Mapped Types and learn how to create sophisticated type transformations.