# Mastering Conditional Types in TypeScript
> As your TypeScript codebase grows, so does the need for abstractions that are both expressive and type-safe. Conditional types unlock a form of type-level…
Published: 2025-06-11
Tags: TypeScript
Canonical: https://alexmlewis.com/writing/mastering-conditional-types-in-typescript
---
> Use conditional types to encode complex type logic—enabling safer, DRY, and expressive APIs at scale.

---

## Why Conditional Types Matter at Scale

As your TypeScript codebase grows, so does the need for abstractions that are both expressive and type-safe. Conditional types unlock a form of **type-level "if/else" logic**, allowing you to build smarter, more maintainable utilities:

```typescript
type IsString<T> = T extends string ? true : false;

type A = IsString<"foo">;   // true
type B = IsString<42>;      // false
```

This simple pattern underpins a wide range of type utilities—from filtering object properties to transforming deeply nested structures—while preserving full type safety.

⸻

Deep Dive: Building a DeepNullable<T> Utility

Suppose you work with a JSON-based API where any property might be null. You want a utility that makes every property in a deeply nested object nullable.

Example Input

```typescript
interface User {
  id: number;
  profile: {
    name: string;
    preferences: {
      theme: "light" | "dark";
      notifications: boolean;
    };
  };
}
```

1. Naïve Approach (Shallow)

```typescript
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

type ShallowNullableUser = Nullable<User>;
// ❌ `profile.preferences.theme` is still non-nullable
```

2. Recursive Conditional Type

```typescript
type DeepNullable<T> = T extends object
  ? { [K in keyof T]: DeepNullable<T[K]> | null }
  : T | null;

type NullableUser = DeepNullable<User>;
```
This produces:

```typescript
{
  id: number | null;
  profile: {
    name: string | null;
    preferences: {
      theme: "light" | "dark" | null;
      notifications: boolean | null;
    } | null;
  } | null;
}
```

How It Works
	1.	T extends object
Checks whether the value is an object (note: this includes arrays and functions).
	2.	Mapped type
Iterates over each key K in T.
	3.	Recursive application
Applies DeepNullable to each property.
	4.	Base case
If T is not an object, it becomes T | null.

⸻

Advanced Patterns

Distributive Conditional Types

Conditional types automatically distribute over unions:

```typescript
type WrapInPromise<T> = T extends any ? Promise<T> : never;

type P = WrapInPromise<string | number>;
// => Promise<string> | Promise<number>
```

This pattern allows you to apply transformations to each member of a union.

Using infer to Extract Types

You can extract inner types from containers like Promise:

```typescript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type U1 = UnwrapPromise<Promise<string>>; // string
type U2 = UnwrapPromise<number>;          // number
```

Combine this with recursion to create utilities like deep unwrapping.

⸻

Pitfalls and Best Practices
	•	Detecting plain objects
T extends object matches arrays and functions. If you want only plain objects, use:

```typescript
T extends Record<string, unknown> ? … : …
```

	•	Recursion depth limits
Deeply nested types can slow down the TypeScript compiler. Scope your utilities carefully, and consider limiting depth.
	•	Circular references
TypeScript can’t fully represent recursive structures with circular references.

⸻

Applying These Utilities in Your Codebase
	1.	Centralize helpers
Place utilities like DeepNullable or UnwrapPromise in a shared types/ module.
	2.	Document intent
Use JSDoc to clarify edge cases, especially around exclusions like arrays or functions.
	3.	Limit scope
Only apply deep utilities where needed. Favor shallow transforms for performance-sensitive areas.

⸻

Key Takeaway

Conditional types are more than just clever syntax—they’re a cornerstone of type-level programming in TypeScript. Mastering them unlocks powerful patterns for enforcing correctness at compile time, reducing bugs and eliminating runtime boilerplate across large-scale projects.