Deep Dive10 min read · Updated 2025-06-01

TypeScript Mapped & Conditional Types: Deep Dive

Master TypeScript mapped types and conditional types — the building blocks of all utility types. Understand keyof, in, infer, and how to write your own type transformations.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

TypeScript Mapped & Conditional Types: Deep Dive

These are the building blocks that all built-in utility types are constructed from.

Mapped Types Fundamentals

A mapped type iterates over the keys of another type:

type User = { id: number; name: string; email: string }

// The shape of Partial<T> type MyPartial<T> = { [K in keyof T]?: T[K] } type PartialUser = MyPartial<User> // { id?: number; name?: string; email?: string }

// Readonly<T> type MyReadonly<T> = { readonly [K in keyof T]: T[K] }

// Map over a union of string literals type Flags = 'debug' | 'verbose' | 'strict' type FlagConfig = { [K in Flags]: boolean } // { debug: boolean; verbose: boolean; strict: boolean }

Modifier Tokens (+ and -)

Add or remove the optional (?) and readonly modifiers:

// -? removes optionality (Required<T>)
type Required<T> = { [K in keyof T]-?: T[K] }

// -readonly removes readonly type Mutable<T> = { -readonly [K in keyof T]: T[K] }

type ReadonlyUser = Readonly<User> type MutableUser = Mutable<ReadonlyUser> // all readonly removed

Key Remapping with as (TypeScript 4.1+)

Rename keys in a mapped type using template literals:

type Getters<T> = {
  [K in keyof T as get${Capitalize<string & K>}]: () => T[K]
}

type UserGetters = Getters<User> // { getId: () => number; getName: () => string; getEmail: () => string }

// Filter keys using never: type OnlyStrings<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K] } type StringFields = OnlyStrings<{ id: number; name: string; active: boolean }> // { name: string }

Conditional Types

// Basic form: T extends U ? X : Y
type IsArray<T> = T extends any[] ? true : false
type A = IsArray<number[]>  // true
type B = IsArray<string>    // false

// NonNullable<T> type NonNullable<T> = T extends null | undefined ? never : T type C = NonNullable<string | null | undefined> // string

// Extract element type type ElementType<T> = T extends (infer E)[] ? E : T type D = ElementType<User[]> // User type E = ElementType<string> // string (no array — falls through)

Distributive Conditional Types

When T is a union, conditional types are distributed over each member:

type ToArray<T> = T extends any ? T[] : never

type Result = ToArray<string | number> // string[] | number[] — distributed! // Not (string | number)[] — that would be non-distributive

// Prevent distribution with tuple wrapping: type ToArrayND<T> = [T] extends [any] ? T[] : never type Result2 = ToArrayND<string | number> // (string | number)[] — non-distributive

The infer Keyword

infer captures a type from within a conditional type:

// Extract the return type of a function
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never

type R1 = MyReturnType<() => string> // string type R2 = MyReturnType<(x: number) => User> // User type R3 = MyReturnType<string> // never (not a function)

// Extract first argument type type FirstArg<T> = T extends (first: infer A, ...rest: any[]) => any ? A : never type F = FirstArg<(x: string, y: number) => void> // string

// Unwrap a Promise type UnPromise<T> = T extends Promise<infer V> ? V : T type U = UnPromise<Promise<User[]>> // User[]

// Extract object value types type ValueOf<T> = T extends Record<string, infer V> ? V : never type V = ValueOf<{ a: number; b: string; c: boolean }> // number | string | boolean

Building Custom Utility Types

// DeepPartial — every nested object's properties are optional
type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T

// PickByValue — pick keys whose values match a type type PickByValue<T, V> = { [K in keyof T as T[K] extends V ? K : never]: T[K] } type StringFields = PickByValue<User & { active: boolean }, string> // { name: string; email: string }

// Merge two types (second overrides first) type Merge<A, B> = Omit<A, keyof B> & B

Practice TypeScript questions at [JSPrep Pro](/auth).

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Deep Dive
We Built a RAG-Powered AI Question Engine Into a JavaScript Interview Platform — Here's Exactly How It Works
12 min read
Build Systems
Monorepo with Turborepo vs Nx: The Complete Comparison (2025)
9 min read
Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read