Intermediate10 questionsFull Guide

TypeScript Generics — Complete Interview Guide

Deep-dive into TypeScript generics — type parameters, constraints, default types, generic functions, interfaces, and classes. Understand how generics enable type-safe reusability and how to answer every interviewer question from basic syntax to advanced inference.

The Mental Model

Generics are type-level parameters — the same concept as function parameters, but for types instead of values. Just as a function like Math.max(a, b) works on any numbers you pass in, a generic like Array<T> works on any element type you supply. The angle-bracket T is a placeholder that gets filled in by the caller, letting you write one implementation that stays type-safe for every concrete type it's used with. Without generics, you'd need to write a separate ArrayOfStrings, ArrayOfNumbers, ArrayOfUsers — generics collapse that explosion into a single, flexible, fully-typed declaration.

The Explanation

Why Generics Exist

Consider a simple identity function. Without generics you must choose between losing type information (using any) or duplicating code (one function per type):

// Bad: any destroys type safety
function identity(arg: any): any {
  return arg;
}
const result = identity("hello"); // result is 'any', not 'string'

// Bad: duplication
function identityString(arg: string): string { return arg; }
function identityNumber(arg: number): number { return arg; }

// Good: one generic function, full type safety
function identity<T>(arg: T): T {
  return arg;
}
const s = identity("hello"); // s is 'string'
const n = identity(42);      // n is 'number'
const u = identity({ id: 1 }); // u is '{ id: number }'

Generic Constraints with extends

Unconstrained generics give you no information about what T supports. Constraints narrow what types are acceptable and unlock properties on T:

// Without constraint — cannot access .length, T could be anything
function logLength<T>(arg: T): T {
  console.log(arg.length); // Error: Property 'length' does not exist on type 'T'
  return arg;
}

// With constraint — T must have a length property
function logLength<T extends { length: number }>(arg: T): T {
  console.log(arg.length); // OK
  return arg;
}

logLength("hello");       // OK — strings have length
logLength([1, 2, 3]);     // OK — arrays have length
logLength({ length: 10 }); // OK — object with length property
logLength(42);            // Error — numbers have no length

The keyof Constraint Pattern

One of the most useful generic patterns: ensuring a key argument actually exists on the object type:

// K must be a key of T — prevents typos and missing-property bugs at compile time
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: "Alice", email: "alice@example.com" };

const name = getProperty(user, "name");  // type is string
const id   = getProperty(user, "id");    // type is number
getProperty(user, "phone"); // Error: Argument of type '"phone"' is not assignable
                            // to parameter of type '"id" | "name" | "email"'

Generic Interfaces and Types

// Generic interface — describes a typed API response
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
  timestamp: Date;
}

// Usage — T is filled in at the call site
type UserResponse  = ApiResponse<User>;
type ProductsResponse = ApiResponse<Product[]>;

// Generic type alias — a reusable Result type (Rust-style)
type Result<T, E = Error> =
  | { ok: true;  value: T }
  | { ok: false; error: E };

// Narrowing works naturally with discriminated unions
function handleResult(r: Result<User>) {
  if (r.ok) {
    console.log(r.value.name); // r.value is User
  } else {
    console.error(r.error.message); // r.error is Error
  }
}

Generic Classes

class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }

  get size(): number {
    return this.items.length;
  }
}

const numStack = new Stack<number>();
numStack.push(1);
numStack.push(2);
numStack.push("three"); // Error — string not assignable to number

const strStack = new Stack<string>();
strStack.push("hello"); // OK

Default Type Parameters

TypeScript 2.3+ supports default types for generics, making them optional at the call site:

// E defaults to Error if not supplied
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };

// Both are valid
type StringResult = Result<string>;        // E = Error
type CustomResult = Result<string, string>; // E = string

// Generic with default in function
function createPair<A, B = A>(first: A, second?: B): [A, B | undefined] {
  return [first, second];
}

Multiple Type Parameters and Inference

// TypeScript infers T and U from the arguments — no need to annotate at call site
function zip<T, U>(a: T[], b: U[]): [T, U][] {
  return a.map((item, i) => [item, b[i]]);
}

const pairs = zip([1, 2, 3], ["a", "b", "c"]);
// pairs is inferred as [number, string][] — no annotation needed

// Curried generic for partial application
const makeTransform = <T>() => <U>(fn: (input: T) => U) => fn;
const stringTransform = makeTransform<string>();
const toNumber = stringTransform(s => parseInt(s, 10)); // (s: string) => number

Common Misconceptions

⚠️

Many developers think generics are just 'TypeScript's version of any' — the opposite is true. any discards type information entirely. Generics preserve and thread type information through a function or structure so the caller retains full type safety.

⚠️

Many developers think you must always annotate the type parameter when calling a generic function — TypeScript's type inference usually figures out T from the argument. identity('hello') infers T = string automatically; you only need explicit annotation when inference fails.

⚠️

Many developers think generic constraints (T extends Something) mean T IS Something — T is constrained to be assignable to Something, not exactly Something. T extends { length: number } means T must have at least a length property; it can have many other properties too.

⚠️

Many developers think generic classes lock a type at class definition time — the type is locked per instance. new Stack<number>() creates a stack of numbers; new Stack<string>() creates a separate stack of strings. Both come from the same class definition.

⚠️

Many developers think you need a separate overload for every return type — generics collapse many overloads into one type-safe signature. Instead of three overloads for string, number, and boolean, one generic captures all three.

⚠️

Many developers avoid generics because they look complex — the most useful patterns (identity, keyof constraint, ApiResponse<T>) are short, predictable, and covered in TypeScript's standard library (Array<T>, Promise<T>, Record<K, V>).

Where You'll See This in Real Code

API client wrappers: production codebases define fetchJson<T>(url: string): Promise<T> so every endpoint call is fully typed without casting — the response shape is encoded in the call site, not inside the utility.

React useState hook: useState<User | null>(null) is generics in action — the state is typed as User | null even though useState itself is a generic function in React's type definitions.

Repository pattern: data access layers define Repository<T> with methods like findById(id: string): Promise<T | null> — one class services every entity type while preserving type safety.

Form libraries: React Hook Form and Formik use generics extensively — useForm<LoginFormValues>() types every field, error, and submission handler automatically from a single type argument.

Utility functions: a deeply typed pick(obj, keys) function uses generics and keyof constraints to return only the selected properties with their correct types — impossible to express safely without generics.

Event emitters: typed EventEmitter<{ click: MouseEvent; keydown: KeyboardEvent }> uses generics to ensure on('click', handler) always infers handler's argument as MouseEvent, not the generic Event.

Interview Cheat Sheet

  • Basic syntax: function fn<T>(arg: T): T — T is a type parameter filled in by the caller
  • Constraint: <T extends SomeType> — T must be assignable to SomeType
  • keyof constraint: <T, K extends keyof T> — K must be an actual key of T
  • Generic interface: interface Box<T> { value: T } — fill in T at usage: Box<string>
  • Default type param: type Result<T, E = Error> — E is optional, defaults to Error
  • TypeScript usually infers T from arguments — explicit annotation only needed when inference fails
  • Generic classes: new Stack<number>() — type is locked per instance, not per class
  • Multiple type params: <T, U> — each parameter inferred independently
  • Generics are erased at runtime — they exist only in the TypeScript compiler
💡

How to Answer in an Interview

  • 1.Lead with the problem generics solve: 'Without generics, I'd choose between any (unsafe) or duplicating my function for every type. Generics let me write the function once and preserve type safety for every caller.'
  • 2.Walk through the identity function example — it's the canonical teaching example and demonstrates type inference: 'I don't have to write identity<string>("hello") — TypeScript infers T = string from the argument.'
  • 3.Know the keyof pattern by heart — it's one of the most common generic patterns in real codebases. 'K extends keyof T ensures the key argument actually exists on the object at compile time, preventing entire categories of runtime errors.'
  • 4.Distinguish generics from any clearly: 'any tells TypeScript to stop caring about the type. A generic T says: I don't know what this type is yet, but track it for me and make sure it's consistent throughout this function call.'
  • 5.Mention real-world examples from standard lib: 'Promise<T>, Array<T>, Map<K, V> — every JavaScript developer uses generics daily through the standard library. Writing your own is just applying the same pattern.'

Practice Questions

10 questions
#01
MediumGeneric ConstraintsPRO

Missing keyof constraint — unsafe property access

#02

Generic identity — T resolves to the argument type

MediumGenerics & Bounds
MicrosoftGoogleMeta
#03
HardGeneric ConstraintsPRO

Missing extends object constraint — primitive passed to object utility

#04
HardGeneric ConstraintsPRO

Generic function returns any instead of constrained type

#05

What are generics and why are they useful in TypeScript?

EasyGenerics PRO💡 Type parameters that make functions/classes/interfaces reusable across multiple types while preserving type safety
#06

What are generic constraints and how do you use the extends keyword with them?

EasyGenerics PRO💡 extends limits which types a type parameter can be — ensures the type has the properties you need
#07

What are default type parameters in TypeScript generics?

EasyGenerics PRO💡 Like default function parameters — a fallback type when the type argument is not specified
#08

What is conditional generic typing — how do you write T extends U ? X : Y?

EasyGenerics PRO💡 Distribute over union members; resolve to different types based on whether T satisfies U
#09

How do generic functions differ from generic types? Show examples of each.

MediumGenerics PRO💡 Generic functions infer T at the call site; generic types require you to pass T explicitly (or let the constructor infer)
#10

What is the Partial, Required, and Readonly pattern when building generic utility functions?

MediumGenerics PRO💡 Combine utility types in function signatures to produce modified shapes — common in config and builder patterns

Related Topics

TypeScript Types vs Interfaces — Complete Interview Guide
Intermediate·8–12 Qs
TypeScript Mapped Types — Complete Interview Guide
Advanced·6–10 Qs
TypeScript Utility Types — Complete Interview Guide
Intermediate·6–10 Qs
TypeScript Conditional Types — Complete Interview Guide
Advanced·6–10 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz