TypeScript generics explained

Can you explain how conditional types work in TypeScript? I keep getting confused by the infer keyword.
06:10
Aria06:10

Conditional types in TypeScript let you choose a type based on a condition — they follow the same logic as a ternary expression.

The basic syntax is:

typescript
type IsString<T> = T extends string ? 'yes' : 'no';

type A = IsString<string>;  // 'yes'
type B = IsString<number>;  // 'no'

The infer keyword is where it gets powerful — it lets you extract a type from within the condition:

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

type Fn = () => { id: number; name: string };
type Result = ReturnType<Fn>; // { id: number; name: string }

Think of `infer R` as: "if this matches, capture whatever is in that position as R."

Want me to show more practical examples — like extracting Promise types or array element types?

Yes please! Show me how to extract the element type from an array or Promise.
06:12
Aria06:12

Great use cases! Here are both patterns:

Unwrap array element type:

typescript
type ElementOf<T> = T extends Array<infer E> ? E : never;

type Names = string[];
type Name = ElementOf<Names>; // string

type Nested = [number, string, boolean];
type First = ElementOf<Nested>; // number | string | boolean

Unwrap Promise value:

typescript
type Awaited<T> = T extends Promise<infer V> ? V : T;

type ApiResult = Promise<{ id: number; email: string }>;
type Data = Awaited<ApiResult>; // { id: number; email: string }

Both combined — async function return value:

typescript
type AsyncReturnType<T extends (...args: any[]) => Promise<any>> =
  T extends (...args: any[]) => Promise<infer R> ? R : never;

async function fetchUser() {
  return { id: 1, name: 'Sofia', role: 'admin' };
}

type User = AsyncReturnType<typeof fetchUser>;
// { id: number; name: string; role: string }

Note: TypeScript 4.5+ ships with a built-in `Awaited` utility type, so you don't need to write it yourself. But understanding how it works helps you build your own patterns.

This is really clear. One more — how do distributive conditional types work?
06:18
Aria06:19

Distributive conditional types are one of TypeScript's more surprising behaviors — and very useful once you understand them.

When you write a conditional type where the checked type is a bare type parameter, TypeScript automatically distributes it over union types:

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

// With a union:
type Result = ToArray<string | number>;
// Distributes to: ToArray<string> | ToArray<number>
// Final result:   string[] | number[]

To prevent distribution, wrap the type parameter in a tuple:

typescript
type NoDistribute<T> = [T] extends [any] ? T[] : never;

type Result2 = NoDistribute<string | number>;
// Does NOT distribute — checks the union as a whole
// Result: (string | number)[]

Practical use — filtering union types:

typescript
type NonNullable<T> = T extends null | undefined ? never : T;

type MaybeString = string | null | undefined;
type CleanString = NonNullable<MaybeString>; // string

This is exactly how TypeScript's built-in `NonNullable` works under the hood.

ChatMind can make mistakes. Verify important information independently.