How do conditional types work, and what does NonNullable use?
Take a moment.
Say your answer out loud or write it down first — retrieval is what makes it stick. Reveal when you're ready.
Short answer
Conditional types pick a branch based on whether T extends a pattern: T extends U ? X : Y. NonNullable<T> is simply T extends null | undefined ? never : T, filtering nullish members out of unions via distributive behavior.
Detailed explanation
Conditionals make types computable: type IsString<T> = T extends string ? true : false. Two mechanics matter most. First, distribution: naked type parameters distribute unions across the check — NonNullable<string | null> becomes (string extends null|undefined ? never : string) | (null → never) = string | never = string. Wrapping the parameter ([T]) disables distribution when you need whole-union comparisons. Second, recursion: conditionals referencing themselves build arbitrary transforms — DeepReadonly walks nested objects until primitives. Combined with infer they express sophisticated queries (Awaited, Get array element type). Practical guardrails: keep conditionals shallow where possible, prefer simpler mapped types when they suffice, and remember never vanishes inside unions which is precisely why filtering patterns work.
Example
// NonNullable's implementation:
type MyNonNullable<T> = T extends null | undefined ? never : T;
type Clean = MyNonNullable<string | null | undefined>; // string
// Recursion example:
type DeepReadonly<T> = T extends (infer U)[]
? readonly DeepReadonly<U>[]
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;Interview tip
Explain distribution with the NonNullable walkthrough step by step — union goes in, each member evaluated separately, nevers drop out. Then show one recursive type briefly.
Common mistakes
- Not knowing naked parameters distribute over unions.
- Writing deeply recursive types where simple mapped types would do.