Skip to main content

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.