TypeScript Generics Explained
Interview Question: "Explain TypeScript generics and when you'd use conditional types."
The Generics Answer
"Generics let you write functions, classes, and types that work with any type while preserving type safety. They're type-level parameters â just like function parameters let you pass different values, generics let you pass different types."
Basic Generic Function
function identity<T>(value: T): T {
return value;
}
const str = identity("hello"); // type: string
const num = identity(42); // type: number"Without generics, you'd either lose type information (using
any) or write separate functions for each type. Generics give you reusability without sacrificing safety."
Generic Constraints
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(value: T): T {
console.log(value.length);
return value;
}
logLength("hello"); // OK â string has .length
logLength([1, 2, 3]); // OK â array has .length
logLength(42); // Error â number has no .length"Constraints narrow what a generic accepts.
T extends HasLengthmeans T must have alengthproperty. You get autocomplete and type checking while keeping it generic."
Real-World: Type-Safe API Client
interface ApiEndpoints {
"/users": { response: User[]; params: { role?: string } };
"/users/:id": { response: User; params: { id: string } };
"/posts": { response: Post[]; params: { limit?: number } };
}
async function api<E extends keyof ApiEndpoints>(
endpoint: E,
params?: ApiEndpoints[E]["params"]
): Promise<ApiEndpoints[E]["response"]> {
const response = await fetch(buildUrl(endpoint, params));
return response.json();
}
const users = await api("/users", { role: "admin" }); // type: User[]
const post = await api("/posts", { limit: 10 }); // type: Post[]
await api("/users", { limit: 10 }); // Error â limit doesn't exist on /users paramsGeneric React Components
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item) => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
// Usage â T is inferred as User
<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
keyExtractor={(user) => user.id}
/>Conditional Types
"Conditional types are the
if/elseof the type system. They let types compute other types based on conditions."
Basic Syntax
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"infer â Extracting Types from Patterns
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never;
type A = ReturnTypeOf<() => string>; // string
type B = ReturnTypeOf<(x: number) => boolean>; // boolean
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type C = UnwrapPromise<Promise<string>>; // string
type D = UnwrapPromise<number>; // number"
inferis pattern matching for types. It 'captures' a type from a structural position.infer Rin a function return position extracts the return type."
Distributive Conditional Types
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// Distributes: string[] | number[] (NOT (string | number)[])"When a conditional type acts on a union, it distributes â applies independently to each union member. This is usually what you want but can be surprising."
Mapped Types
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Partial<T> = { [K in keyof T]?: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface User {
id: number;
name: string;
email: string;
age: number;
}
type StringFields = PickByType<User, string>;
// { name: string; email: string }Discriminated Unions
type Result<T> =
| { status: "success"; data: T }
| { status: "error"; error: Error }
| { status: "loading" };
function handleResult<T>(result: Result<T>) {
switch (result.status) {
case "success":
console.log(result.data); // TypeScript knows data exists here
break;
case "error":
console.log(result.error); // TypeScript knows error exists here
break;
case "loading":
break; // No data or error available
}
}"Discriminated unions combined with generics are how you model state machines in TypeScript. The
statusfield acts as the discriminant â TypeScript narrows the type in each branch automatically."
What Interviewers Look For
- Explains generics as type parameters â not just "makes things reusable"
- Shows constraints â
extendsfor narrowing generic types - Real-world examples â API clients, React components, state machines
- Understands
inferâ pattern matching for types - Knows when NOT to use generics â over-generic code is worse than duplicated code
Common Mistakes
- Over-generic code â making everything generic when concrete types are fine
- Using
anyas an escape hatch â defeats the purpose of TypeScript - Generic naming conventions â
Tfor type,Kfor key,Vfor value,Efor element. Don't useTfor everything in multi-generic signatures - Forgetting distribution â conditional types on unions distribute by default
- Not using constraints â a bare
<T>gives you no useful properties to work with
Follow-Up Questions
"How do you avoid over-engineering with generics?"
"I follow the rule of three â don't make something generic until you have three concrete use cases. Start with specific types, then extract a generic when the pattern repeats. Over-generic code is harder to read, harder to debug, and the type errors are incomprehensible."
"What's the difference between unknown and any?"
"
anydisables type checking â anything goes.unknownis type-safe â you must narrow it before using it. Useunknownfor values from external boundaries (API responses, user input) and narrow with type guards."
"When would you use a mapped type vs a conditional type?"
"Mapped types transform the shape of an object type â adding/removing modifiers, filtering keys, remapping values. Conditional types branch based on type relationships. Often they're combined: a mapped type with a conditional type in the value position to transform different properties differently."
Red Flags
- Saying generics are "like templates in C++" without explaining the structural typing difference
- Using
anyinstead of generics to make code flexible - Cannot explain
inferor give an example - No awareness of discriminated unions for state modeling
- Treating TypeScript as "JavaScript with type annotations" rather than leveraging the type system