Lesson 38 of 80
A conditional type applied to a union doesn't check the union as a whole — it silently runs once per member and unions the results back together. Knowing this is the difference between a utility type that works and one that mysteriously always says no.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
The conditional-types lesson mentioned this in passing as "what people usually miss." It deserves its own lesson because it changes how a conditional type actually behaves, not just what it returns:
type ToArray<T> = T extends unknown ? T[] : never;
type A = ToArray<string>; // string[]
type B = ToArray<string | number>; // string[] | number[] — NOT (string | number)[]
You might expect ToArray<string | number> to check "does string | number extend unknown?" once, and produce (string | number)[]. That's not what happens. Because T is a bare, naked type parameter on the left of extends, TypeScript distributes the conditional over each member of the union separately: it computes ToArray<string>, computes ToArray<number>, then unions the two results back together.
type Exclude<T, U> = T extends U ? never : T;
type Status = "idle" | "loading" | "error" | "success";
type Pending = Exclude<Status, "error" | "success">;
// "idle" | "loading"
This only works because of distribution. TypeScript checks each member of Status individually against the condition: "idle" extends "error" | "success" ? never : "idle" resolves to "idle", so it survives. "error" extends "error" | "success" ? never : "error" resolves to never. Union the four results together, and never members drop out of a union automatically — leaving exactly "idle" | "loading". NonNullable<T> is the same trick with one fixed condition: T extends null | undefined ? never : T, applied member-by-member to strip out just the nullish parts of a union.
type ToArrayNonDistributive<T> = [T] extends [unknown] ? T[] : never;
type C = ToArrayNonDistributive<string | number>; // (string | number)[]
Wrapping both sides of extends in a one-element tuple ([T] and [unknown]) makes the type parameter no longer "naked" — it's now nested inside a tuple type — which switches distribution off. TypeScript checks the whole union against the whole condition in one shot, instead of member-by-member. This is the standard, idiomatic way to deliberately disable distribution when you actually want ToArray<string | number> to mean "an array containing either," not "either kind of array."
extends inside a generic conditional type, distribution is already on. Most people discover this by being confused why their conditional type "checks each option separately" when they never asked it to.ToArray<never> distributes to never, not never[]. A union with zero members (never is the empty union) distributes to zero results, which is itself never — a genuinely surprising edge case that trips people up specifically because it doesn't follow the "one result per member" pattern intuitively when there are no members at all.Exclude, Extract, and NonNullable are not special compiler primitives — they're ordinary conditional types that rely entirely on distribution to work member-by-member across a union. Understanding distribution is understanding how three of TypeScript's most-used built-in utility types are actually implemented.[T] extends [U] tuple-wrapping trick is idiomatic, not a hack. It shows up constantly in library type definitions specifically to force a whole-union check instead of a per-member one, and recognizing it on sight saves real confusion when reading other people's advanced types."Why does Exclude<T, U> work the way it does?" or "what does [T] extends [U] mean and why would you write it that way?" are both direct probes for whether you understand distribution specifically, versus just knowing conditional types exist. It's one of the most commonly-misunderstood corners of the type system precisely because the behavior is implicit — nothing in T extends U ? X : Y visually signals that it's about to run once per union member.