AtomLearnAtomLearn
DashboardGoalsPathAchievementsReviewSign In
TypeScriptNot Started

Generics

Write reusable typed functions and interfaces with type parameters — the `<T>` pattern explained

0%
Knowledge0%
Learn & Drill
Fluency0%
Drill & Speed
Retention0%
Mastery & Review
Confidence0%
All modes
Practice

Free tier: read the explanation here. Upgrade to Pro for Drills, Speed challenges & Mastery badges.

Upgrade
Knowledge
Fluency
Retention

Knowledge Debt detected

You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.

Explanation

Generics let you write functions and types that work with *any* type while preserving type information. Think of T as a placeholder that gets filled in at call time.

The problem generics solve:

typescript // Without generics — loses type information function identity(val: any): any { return val; } const result = identity(42); // result is 'any' — useless // With generics — type is preserved function identity<T>(val: T): T { return val; } const result = identity(42); // result is 'number' const str = identity('hello'); // result is 'string'

Generic functions:

typescript function firstItem<T>(arr: T[]): T | undefined { return arr[0]; } const first = firstItem([1, 2, 3]); // type: number | undefined const name = firstItem(['Alice', 'Bob']); // type: string | undefined

Generic interfaces:

typescript interface ApiResponse<T> { data: T; status: number; message: string; } // Use it with any data shape const userResponse: ApiResponse<User> = { data: { id: 1, name: 'Alice' }, status: 200, message: 'OK', };

Type constraints — restrict what types T can be: ``typescript // T must have a .length property function logLength<T extends { length: number }>(val: T): void { console.log(val.length); } logLength('hello'); // OK — string has .length logLength([1, 2, 3]); // OK — array has .length // logLength(42); // Error — number has no .length // T must be a key of object U function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const name = getProperty({ name: 'Alice', age: 30 }, 'name'); // string

Multiple type parameters:

typescript function zip<A, B>(a: A[], b: B[]): [A, B][] { return a.map((item, i) => [item, b[i]]); } zip([1, 2], ['a', 'b']); // [number, string][]

Examples

Generic function and interface

Generics let you write `Stack<T>` once and use it as `Stack<number>`, `Stack<string>`, `Stack<User>` — full type safety for each.

// Generic stack — works for any element type
interface Stack<T> {
  push(item: T): void;
  pop(): T | undefined;
  peek(): T | undefined;
  size(): number;
}

class TypedStack<T> implements 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]; }
  size(): number { return this.items.length; }
}

const numStack = new TypedStack<number>();
numStack.push(1);
numStack.push(2);
console.log(numStack.pop()); // 2 — TypeScript knows this is number

const strStack = new TypedStack<string>();
strStack.push('hello');
// strStack.push(42); // Error: Argument of type 'number' is not assignable to type 'string'

Constrained generics with keyof

`extends` on a type parameter is a constraint, not inheritance — it says "T must have at least these properties." `keyof T` produces a union of all property names of T.

// Constraint: T must have a .length property
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

console.log(longest('hello', 'hi'));          // 'hello'
console.log(longest([1, 2, 3], [4, 5]));      // [1, 2, 3]
// console.log(longest(1, 2));  // Error: 'number' has no 'length'

// keyof — pick a property safely
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map(item => item[key]);
}

const users = [
  { id: 1, name: 'Alice', age: 30 },
  { id: 2, name: 'Bob',   age: 25 },
];

const names = pluck(users, 'name'); // string[]
const ages  = pluck(users, 'age');  // number[]
// pluck(users, 'email'); // Error: 'email' is not a key of the object type

How well did you understand this?

Next in TypeScript

Type Narrowing

Continue