Generics
Generics
Introduction
In this lesson, you'll learn about generics in TypeScript. Coming from Java, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In Java, you're familiar with generics.
TypeScript has its own approach to generics, which we'll explore step by step.
The TypeScript Way
Let's see how TypeScript handles this concept. Here's a typical example:
// TypeScript generics - erased at compile time
class Box<T> {
constructor(private value: T) {}
getValue(): T { return this.value; }
}
// Constraint (like Java's upper-bounded wildcard)
function printNumbers<T extends number | bigint>(list: T[]): void {
list.forEach(n => console.log(n));
}
// Conditional type (more powerful than wildcards)
type IsNumber<T> = T extends number ? true : false;
const strBox = new Box("hello");
const s: string = strBox.getValue();Comparing to Java
Here's how you might have written similar code in Java:
// Java generics - type erasure at runtime
class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T getValue() { return value; }
}
// Bounded wildcard
void printNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n.doubleValue());
}
}
Box<String> strBox = new Box<>("hello");
String s = strBox.getValue();You may be used to different syntax or behavior.
Generic syntax is almost identical: Box<T>, <T extends Something>
You may be used to different syntax or behavior.
Java erases generic types at runtime (type erasure); TypeScript erases all types at compile time
You may be used to different syntax or behavior.
Java wildcards (? extends Number, ? super T) become TypeScript constraints (T extends number)
You may be used to different syntax or behavior.
TypeScript has conditional types (T extends U ? X : Y) which are more powerful than Java wildcards
You may be used to different syntax or behavior.
TypeScript allows more advanced type-level programming: mapped types, infer keyword, etc.
Step-by-Step Breakdown
1. Generic Functions
Generic functions work the same way — declare a type parameter before the parameter list.
public <T> T identity(T value) {
return value;
}function identity<T>(value: T): T {
return value;
}
// TypeScript infers the type argument:
const s = identity("hello"); // T = string
const n = identity(42); // T = number2. Generic Constraints
Use extends to constrain what types are allowed, just like Java's upper-bounded wildcards.
<T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}// Constrain T to types that have a length property
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest("alice", "bob"); // works — strings have length
longest([1, 2, 3], [4, 5]); // works — arrays have length3. Conditional Types
TypeScript conditional types let you express type logic that has no equivalent in Java generics.
// Unwrap a Promise type
type Awaited<T> = T extends Promise<infer U> ? U : T;
type A = Awaited<Promise<string>>; // string
type B = Awaited<number>; // number
// Make all properties optional
type Partial<T> = { [K in keyof T]?: T[K] };
interface User { name: string; age: number; }
type PartialUser = Partial<User>;
// { name?: string; age?: number; }4. Generic Interfaces
TypeScript generic interfaces look and behave like Java generic interfaces.
interface Repository<T> {
T findById(long id);
List<T> findAll();
void save(T entity);
}interface Repository<T> {
findById(id: number): T | null;
findAll(): T[];
save(entity: T): void;
}
class UserRepository implements Repository<User> {
findById(id: number): User | null { return null; }
findAll(): User[] { return []; }
save(user: User): void { }
}Common Mistakes
When coming from Java, developers often make these mistakes:
- Generic syntax is almost identical: Box<T>, <T extends Something>
- Java erases generic types at runtime (type erasure); TypeScript erases all types at compile time
- Java wildcards (? extends Number, ? super T) become TypeScript constraints (T extends number)
Key Takeaways
- Generic syntax <T> and constraints <T extends X> are nearly identical to Java
- Both languages erase types (Java at runtime, TypeScript at compile time)
- TypeScript conditional types replace Java wildcards and add more expressive power
- Generic interfaces and classes translate almost line-for-line