TypeScript Utility Types: Deep Dives into Partial, Pick, Conditional, and Mapped Types
Master TypeScript's type system: understand built-in utility types deeply, build recursive Partial and Required, implement conditional types with infer, create mapped types for transformations, and design type-safe builder patterns with template literal types.
TypeScript's utility types are small, composable transformations of existing types. Partial<T> makes all properties optional. Pick<T, K> keeps only the specified keys. These are useful, but understanding how they work unlocks the ability to build your own โ and that's where TypeScript's type system becomes genuinely powerful.
Built-In Utility Types: How They Work
// TypeScript's built-in utility types โ reimplemented to understand them
// Partial: makes all properties optional
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Required: makes all properties required (removes ?)
type MyRequired<T> = {
[K in keyof T]-?: T[K]; // -? removes the optional modifier
};
// Readonly: makes all properties read-only
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// Mutable: removes readonly (opposite of Readonly)
type Mutable<T> = {
-readonly [K in keyof T]: T[K]; // -readonly removes the modifier
};
// Pick: keep only specified keys
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Omit: remove specified keys
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// Exclude: remove members from a union
type MyExclude<T, U> = T extends U ? never : T;
// Extract: keep members of a union that extend U
type MyExtract<T, U> = T extends U ? T : never;
// Record: create an object type with keys K and values V
type MyRecord<K extends string | number | symbol, V> = {
[P in K]: V;
};
// ReturnType: extract the return type of a function
type MyReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never;
// Parameters: extract the parameter types of a function
type MyParameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// Awaited: unwrap Promise (and nested Promises)
type MyAwaited<T> =
T extends null | undefined ? T
: T extends object & { then(onfulfilled: infer F, ...args: any[]): any }
? F extends (value: infer V, ...args: any[]) => any
? MyAwaited<V>
: never
: T;
Recursive Utility Types
The built-in Partial<T> is shallow โ it only makes top-level properties optional. For nested objects, build a deep version:
// Deep Partial โ makes all nested properties optional
type DeepPartial<T> =
T extends (infer U)[]
? DeepPartial<U>[] // Handle arrays
: T extends object
? { [K in keyof T]?: DeepPartial<T[K]> } // Recurse into objects
: T; // Primitive โ return as-is
// Deep Required โ makes all nested properties required
type DeepRequired<T> =
T extends (infer U)[]
? DeepRequired<U>[]
: T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;
// Deep Readonly
type DeepReadonly<T> =
T extends (infer U)[]
? readonly DeepReadonly<U>[]
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
// Usage
interface Config {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
server: {
port: number;
tls: boolean;
};
}
type PartialConfig = DeepPartial<Config>;
// database?: { host?: string; port?: number; credentials?: { username?: string; ... } }
๐ Looking for a Dev Team That Actually Delivers?
Most agencies sell you a project manager and assign juniors. Viprasol is different โ senior engineers only, direct Slack access, and a 5.0โ Upwork record across 100+ projects.
- React, Next.js, Node.js, TypeScript โ production-grade stack
- Fixed-price contracts โ no surprise invoices
- Full source code ownership from day one
- 90-day post-launch support included
Conditional Types with infer
infer extracts a type from within a conditional type:
// Extract array element type
type ElementOf<T> = T extends (infer E)[] ? E : never;
type StringElement = ElementOf<string[]>; // string
type NumberElement = ElementOf<number[]>; // number
type NeverElement = ElementOf<string>; // never โ not an array
// Extract the first argument type from a function
type FirstArg<T extends (...args: any) => any> =
T extends (first: infer F, ...rest: any[]) => any ? F : never;
function greet(name: string, greeting: string) { return `${greeting}, ${name}`; }
type GreetFirstArg = FirstArg<typeof greet>; // string
// Unwrap a Promise
type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;
type Resolved = UnwrapPromise<Promise<Promise<string>>>; // string
// Extract the value type from a getter function
type ValueOf<T extends { get(): any }> =
T extends { get(): infer V } ? V : never;
interface Repository<T> {
get(): Promise<T>;
save(item: T): Promise<void>;
}
type RepoValue = ValueOf<Repository<string>>; // Promise<string>
type UnwrappedRepoValue = UnwrapPromise<RepoValue>; // string
Mapped Types for API Transformations
// Transform API response types โ convert snake_case keys to camelCase
type CamelCase<S extends string> =
S extends `${infer P}_${infer Q}`
? `${P}${Capitalize<CamelCase<Q>>}`
: S;
type CamelCaseKeys<T> = {
[K in keyof T as CamelCase<string & K>]: T[K] extends object
? CamelCaseKeys<T[K]>
: T[K];
};
interface ApiResponse {
user_id: string;
first_name: string;
last_name: string;
created_at: string;
address_info: {
street_address: string;
postal_code: string;
};
}
type TransformedResponse = CamelCaseKeys<ApiResponse>;
// { userId: string; firstName: string; lastName: string; createdAt: string; addressInfo: { streetAddress: string; postalCode: string } }
// Nullify specific keys
type NullableKeys<T, K extends keyof T> = Omit<T, K> & {
[P in K]: T[P] | null;
};
interface User {
id: string;
name: string;
avatar: string;
bio: string;
}
type UserWithOptionalProfile = NullableKeys<User, "avatar" | "bio">;
// { id: string; name: string; avatar: string | null; bio: string | null }
๐ Senior Engineers. No Junior Handoffs. Ever.
You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.
- MVPs in 4โ8 weeks, full platforms in 3โ5 months
- Lighthouse 90+ performance scores standard
- Works across US, UK, AU timezones
- Free 30-min architecture review, no commitment
Template Literal Types
// Type-safe event emitter using template literal types
type EventName = "click" | "focus" | "blur" | "change";
type EventHandler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur" | "onChange"
// Route builder with type-safe params
type ExtractParams<Path extends string> =
Path extends `${infer _Start}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractParams<`/${Rest}`>]: string }
: Path extends `${infer _Start}:${infer Param}`
? { [K in Param]: string }
: Record<never, never>;
type RouteParams = ExtractParams<"/users/:userId/posts/:postId">;
// { userId: string; postId: string }
function buildUrl<Path extends string>(
path: Path,
params: ExtractParams<Path>
): string {
let result: string = path;
for (const [key, value] of Object.entries(params)) {
result = result.replace(`:${key}`, value as string);
}
return result;
}
// TypeScript knows exactly which params are required
const url = buildUrl("/users/:userId/posts/:postId", {
userId: "123",
postId: "456",
// TypeScript error if you add extra keys or miss required ones
});
// "/users/123/posts/456"
Discriminated Union Helpers
// Extract variants from a discriminated union
type ExtractVariant<T, K extends string> =
T extends { type: K } ? T : never;
type Action =
| { type: "increment"; amount: number }
| { type: "decrement"; amount: number }
| { type: "reset" }
| { type: "set"; value: number };
type IncrementAction = ExtractVariant<Action, "increment">;
// { type: "increment"; amount: number }
// Build a handler map for a discriminated union
type ActionHandlerMap<T extends { type: string }> = {
[K in T["type"]]: (action: ExtractVariant<T, K>) => void;
};
// TypeScript enforces that every variant has a handler
const handlers: ActionHandlerMap<Action> = {
increment: (action) => console.log(action.amount),
decrement: (action) => console.log(action.amount),
reset: (_action) => console.log("reset"),
set: (action) => console.log(action.value),
};
Type-Safe Builder Pattern
// Builder pattern with TypeScript tracking what's been set
// Prevents calling .build() before required fields are set
type BuilderState = {
name: boolean;
url: boolean;
method: boolean;
};
class RequestBuilder<State extends BuilderState = { name: false; url: false; method: false }> {
private config: Partial<{ name: string; url: string; method: string }> = {};
setName(name: string): RequestBuilder<State & { name: true }> {
this.config.name = name;
return this as any;
}
setUrl(url: string): RequestBuilder<State & { url: true }> {
this.config.url = url;
return this as any;
}
setMethod(method: string): RequestBuilder<State & { method: true }> {
this.config.method = method;
return this as any;
}
// build() is only available when all required fields are set
build(
this: RequestBuilder<{ name: true; url: true; method: true }>
): { name: string; url: string; method: string } {
return this.config as { name: string; url: string; method: string };
}
}
// TypeScript prevents calling build() before all fields are set
const request = new RequestBuilder()
.setName("fetch-user")
.setUrl("/api/users")
.setMethod("GET")
.build(); // โ
All required fields set
const incomplete = new RequestBuilder()
.setName("fetch-user")
.build(); // โ TypeScript Error: Property 'build' does not exist
See Also
- TypeScript Advanced Patterns โ conditional types in practice
- TypeScript Generics: Advanced Patterns โ generic constraints
- TypeScript Decorators โ metadata and decoration patterns
- TypeScript Testing Patterns โ testing typed code
Working With Viprasol
Deep TypeScript type safety โ discriminated unions, conditional types, mapped transformations โ catches entire classes of runtime bugs at compile time. Our TypeScript engineers design type systems that express business invariants in the type layer, so incorrect states are literally inexpressible in the code.
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need a Modern Web Application?
From landing pages to complex SaaS platforms โ we build it all with Next.js and React.
Free consultation โข No commitment โข Response within 24 hours
Need a custom web application built?
We build React and Next.js web applications with Lighthouse โฅ90 scores, mobile-first design, and full source code ownership. Senior engineers only โ from architecture through deployment.