Mapped & Conditional Types
Transforming types dynamically with mapped types, conditionals, and infer
TypeScript types can compute other types. This is type-level programming.
What you'll learn
Create mapped types with in keyof
Build conditional types with extends ? :
Extract types with the infer keyword
Combine template literals with mapped types
Implement recursive types (DeepPartial, DeepReadonly)
Transform API responses at the type level
Mapped Types
Iterate over keys and transform each property.
type Readonlyify<T> = {
readonly [K in keyof T]: T[K]
}
type Stringify<T> = {
[K in keyof T]: string
}
interface User {
id: number
name: string
email: string
}
type ReadonlyUser = Readonlyify<User>
// { readonly id: number; readonly name: string; readonly email: string }
type StringUser = Stringify<User>
// { id: string; name: string; email: string }
// Key remapping with as
type Getters<T> = {
[K in keyof T as `get${Capitalize<K & string>}`]: () => T[K]
}
type UserGetters = Getters<User>
// { getGetId: () => number; getName: () => string; getEmail: () => string }
Adding and Removing Modifiers
// Make all properties optional
type MakeOptional<T> = {
[K in keyof T]?: T[K]
}
// Remove optionality
type MakeRequired<T> = {
[K in keyof T]-?: T[K]
}
// Remove readonly
type MakeMutable<T> = {
-readonly [K in keyof T]: T[K]
}
type WithAge = MakeRequired<{ name?: string; age?: number }>
// { name: string; age: number }
Conditional Types
Types that depend on a condition.
type IsString<T> = T extends string ? "yes" : "no"
type A = IsString<"hello"> // "yes"
type B = IsString<number> // "no"
// Filter types from a union
type ExtractString<T> = T extends string ? T : never
type C = ExtractString<string | number | boolean>
// string
// Distribute over unions
type ToArray<T> = T extends unknown ? T[] : never
type D = ToArray<string | number>
// string[] | number[] (distributes)
The infer Keyword
Extract types from other types in conditional branches.
// Extract return type
type Return<T> = T extends (...args: unknown[]) => infer R ? R : never
type R = Return<() => string> // string
// Extract array element type
type Element<T> = T extends (infer U)[] ? U : never
type E = Element<string[]> // string
// Extract Promise value
type Unwrap<T> = T extends Promise<infer U> ? U : T
type F = Unwrap<Promise<number>> // number
type G = Unwrap<string> // string
// Extract first argument of a function
type FirstArg<T> = T extends (first: infer F, ...rest: unknown[]) => unknown
? F
: never
type H = FirstArg<(id: number, name: string) => void> // number
Gotcha
Conditional types distribute over unions. Wrap with [T] extends [U] to
prevent distribution when you need to check the whole union.
Template Literal + Mapped Types
interface CSSProperties {
margin: string
padding: string
color: string
}
// Create event handlers from property names
type CSSEvents = {
[K in keyof CSSProperties as `on${Capitalize<K>}Change`]: (
value: CSSProperties[K]
) => void
}
// { onMarginChange: (value: string) => void; ... }
Recursive Types
Deep transformations that recurse into nested objects.
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
interface Config {
server: { host: string; port: number }
auth: { token: string; ttl: number }
}
type PartialConfig = DeepPartial<Config>
// { server?: { host?: string; port?: number }; auth?: { token?: string; ttl?: number } }
// Deep non-nullable
type DeepNonNullable<T> = {
[K in keyof T]: T[K] extends object
? DeepNonNullable<NonNullable<T[K]>>
: NonNullable<T[K]>
}
Real-World: API Response Transformer
Transform an API schema into a frontend-friendly shape.
interface APIPost {
id: number
title: string
author_id: number
created_at: string
updated_at: string | null
}
// Rename snake_case to camelCase, unwrap timestamps to Date
type SnakeToCamel<S extends string> = S extends `${infer T}_${infer U}`
? `${T}${Capitalize<SnakeToCamel<U>>}`
: S
type Transformed<T> = {
[K in keyof T as SnakeToCamel<K & string>]: T[K] extends string
? T[K]
: T[K] extends number
? T[K]
: T[K] extends null | undefined
? never
: T[K]
}
type FrontendPost = Transformed<APIPost>
// { id: number; title: string; authorId: number; createdAt: string; updatedAt: string }
// Map to API response wrapper
type APIResponse<T> = {
data: T[]
meta: { total: number; page: number }
}
type PostsResponse = APIResponse<FrontendPost>
Implement DeepPartial
Code
typescript
1type DeepPartial<T> = {2// Implement recursive mapped type3// Hint: check if property is array or object4}Key Takeaways
Mapped types transform each property using [K in keyof T] syntax.
Modifiers ? and readonly can be added or removed (with -).
Conditional types branch on T extends U ? A : B.
infer captures types from within conditional branches.
Recursive types like DeepPartial apply mapped types at every nesting level.
Key remapping with as enables renaming and filtering properties at the type level.