Tags: typescript, generics, type-system, utility-types, typing
Level: advanced
TL;DR: Advanced type-level techniques covering conditional types, template literals, type narrowing, distributive mechanics, and type-safe invariants.
Conditional types select types using the syntax T extends U ? X : Y.
The infer keyword introduces runtime-independent type variables within conditional type checks.
Naked type parameters distribute automatically across union members in conditional types.
Distributive behavior is disabled by wrapping terms in tuples: [T] extends [any].
Mapped types transform property keys and values using [K in keyof T].
Key remapping syntax [K in keyof T as NewKey] enables filtering and transforming object keys.
Template literal types combine string literals and union types into new string sets.
keyof extracts public keys; typeof captures runtime values into the type space.
Indexed access types query nested structures using Type['prop'].
Branded or nominal types enforce semantic boundaries on primitive types using unique phantom properties.
Q: How do distributive conditional types behave and how can you turn off distribution?R: Naked type parameters over a union evaluate each union variant individually and union the outcomes. Wrapping both sides in square brackets, like [T] extends [never], disables distribution and evaluates the union as a single entity.
Q: What is the difference between unknown and any?R: any turns off compiler checks completely and propagates unchecked across expressions. unknown is the type-safe top type that requires narrowing or type assertions before performing operations.
Q: How does infer work inside a conditional type?R: It extracts an inner type variable from a structural pattern match during evaluation, such as unpacking promise payloads or function return types.
Q: How do you enforce exhaustive checking with TypeScript discriminated unions?R: I assign the default case in a switch statement to a value of type never. If a variant is missed, the compiler raises a type error.
Checking for never with an unbracketed conditional T extends never resolves immediately to never due to empty union distribution.
Overusing type assertions with as bypasses the type-checker and hides invalid runtime data shapes.
Using any inside generic parameters loses downstream inference chains and breaks type safety.
Forgetting that TypeScript uses structural typing, which allows excess properties in variables assigned across multiple steps.
Relying on keyof over object unions: it resolves to the intersection of keys rather than the union of keys.
// Type unwrapping with infer
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
// Prevent conditional distribution
type IsNever<T> = [T] extends [never] ? true : false;
// Discriminated union and exhaustive check
type Action = { type: 'ADD'; payload: number } | { type: 'RESET' };
function reducer(action: Action): number {
switch (action.type) {
case 'ADD':
return action.payload;
case 'RESET':
return 0;
default: {
const _exhaustive: never = action;
return _exhaustive;
}
}
}
// Key remapping and filtering
type OnlyGetters<T> = {
[K in keyof T as K extends `get${string}` ? K : never]: T[K];
};
// Nominal/Branded typing
type Brand<K, T> = K & { readonly __brand: T };
type UserId = Brand<string, 'UserId'>;
[ ] Variance rules: covariance, contravariance, bivariance, and invariance
[ ] Function overload signatures versus union parameters
[ ] Performance costs of deep recursive conditional types in large codebases
[x] Basic generics syntax and generic constraints with extends
[x] Standard utility types: Pick, Omit, Record, ReturnType, and Partial