DEV Community

Srashti Gupta
Srashti Gupta

Posted on

WHY we use Mapped Types &Conditional Types in TS?

We Use Mapped & Conditional Types in TypeScript (Real-World Examples)

When building real applications, we rarely deal with static types.
Data changes based on:

  • API responses
  • User roles
  • Form states
  • Backend validations

Mapped Types and Conditional Types help us reuse and transform existing types instead of rewriting them again and again.

Let’s understand why they exist, using simple real-world examples.


1. Why Mapped Types?

Problem (Real Life)

You have a User model coming from the backend:

type User = {
  id: number
  name: string
  email: string
  isActive: boolean
}
Enter fullscreen mode Exit fullscreen mode

Now in real projects, you often need:

  • A form version (all fields optional)
  • A readonly version (API response)
  • A preview version (only some fields)

Without mapped types ❌ you’d rewrite types manually — which is error-prone.


Solution: Mapped Types ✅

Example 1: Form State (All Optional)

type UserForm = {
  [K in keyof User]?: User[K]
}
Enter fullscreen mode Exit fullscreen mode

Now:

  • Adding/removing fields in User automatically updates UserForm
  • No duplicate type maintenance

This is exactly what Partial<User> does internally.


Example 2: Read-Only API Response

type ReadonlyUser = {
  readonly [K in keyof User]: User[K]
}
Enter fullscreen mode Exit fullscreen mode

Why?

  • Prevents accidental mutation of API data
  • Very common in frontend apps

Example 3: Editable Fields Only

type EditableUser = {
  [K in "name" | "email"]: User[K]
}
Enter fullscreen mode Exit fullscreen mode

Used when:

  • Only some fields are editable
  • Others (like id) should not be touched

2. Why Conditional Types?

Problem (Real Life)

Different APIs or functions return different data types based on input.

Example:

  • If user is "admin" → return full access
  • If user is "guest" → limited access

Solution: Conditional Types ✅

Example 1: Role-Based Data

type Role = "admin" | "user"

type Permissions<T> = T extends "admin"
  ? { canDelete: true; canEdit: true }
  : { canDelete: false; canEdit: true }
Enter fullscreen mode Exit fullscreen mode

Usage:

type AdminPermissions = Permissions<"admin">
type UserPermissions = Permissions<"user">
Enter fullscreen mode Exit fullscreen mode

Why this matters:

  • Type safety at compile time
  • No runtime role mistakes

Example 2: API Response Type

type ApiResponse<T> =
  T extends "success"
    ? { data: string }
    : { error: string }
Enter fullscreen mode Exit fullscreen mode
type Success = ApiResponse<"success">
type Failure = ApiResponse<"error">
Enter fullscreen mode Exit fullscreen mode

This matches how real APIs behave.


3. Why infer? (Very Practical Example)

Problem

You want to extract the data type from an API function automatically.

function fetchUser() {
  return { id: 1, name: "Srashti" }
}
Enter fullscreen mode Exit fullscreen mode

Manually writing types ❌ is repetitive.


Solution: infer

type ExtractReturn<T> =
  T extends (...args: any[]) => infer R ? R : never

type UserData = ExtractReturn<typeof fetchUser>
Enter fullscreen mode Exit fullscreen mode

Why this is powerful:

  • Auto-syncs with function changes
  • No manual updates

This is how ReturnType<T> works internally.


4. Why Distributive Conditional Types?

Problem

You have a union type and want logic applied to each member.

type Input = string | number
Enter fullscreen mode Exit fullscreen mode

Solution

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

type Result = ToArray<string | number>
// string[] | number[]
Enter fullscreen mode Exit fullscreen mode

Why this is useful:

  • Validating inputs
  • Transforming API payloads
  • Working with union types cleanly

5. Real Use of Utility Types (Behind the Scenes)

All of these are built using Mapped + Conditional Types:

Partial<T>
Pick<T, K>
Omit<T, K>
Exclude<T, U>
Extract<T, U>
ReturnType<T>
Enter fullscreen mode Exit fullscreen mode

Example from real apps:

type UpdateUserPayload = Omit<User, "id">
Enter fullscreen mode Exit fullscreen mode

Used when:

  • Backend auto-generates id
  • Frontend shouldn’t send it

6. Real-World Summary (Why We Actually Use Them)

We use Mapped Types when:

  • One base model has multiple variations
  • Forms, DTOs, API models differ slightly
  • We want DRY (Don’t Repeat Yourself) types

We use Conditional Types when:

  • Output type depends on input
  • Role-based or state-based logic
  • API responses vary

Final Thought

Mapped Types and Conditional Types are not “advanced for no reason”.
They exist because real applications are dynamic, and TypeScript helps us model that safely and cleanly.

Once you start using them, you’ll:

  • Write fewer bugs
  • Remove duplicate types

* Scale your codebase confidently


Top comments (0)