Generics
Generics
Section titled โGenericsโWhat it is
Section titled โWhat it isโGenerics allow creating reusable components that work with multiple types while maintaining type safety. They use type parameters (like <T>) to represent types that will be specified when the component is used.
Before this feature
Section titled โBefore this featureโWithout generics, youโd duplicate code or use any:
// Duplicationfunction numberIdentity(arg: number): number { return arg;}function stringIdentity(arg: string): string { return arg;}
// Or lose type safetyfunction identity(arg: any): any { return arg;}After this feature
Section titled โAfter this featureโGenerics provide reusability with type safety:
// Generic functionfunction identity<T>(arg: T): T { return arg;}
const num = identity<number>(42); // type: numberconst str = identity<string>("hello"); // type: stringconst auto = identity(true); // type inferred: boolean
// Generic interfaceinterface Box<T> { value: T;}
const numberBox: Box<number> = { value: 123 };const stringBox: Box<string> = { value: "text" };
// Generic classclass DataStore<T> { private data: T[] = [];
add(item: T): void { this.data.push(item); }
get(index: number): T | undefined { return this.data[index]; }}
const numberStore = new DataStore<number>();numberStore.add(1);numberStore.add(2);
// Multiple type parametersfunction pair<T, U>(first: T, second: U): [T, U] { return [first, second];}
const result = pair<string, number>("age", 25);
// Generic constraintsinterface Lengthwise { length: number;}
function logLength<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg;}
logLength("hello"); // OKlogLength([1, 2, 3]); // OKlogLength({ length: 10 }); // OKlogLength(42); // Error: number has no lengthWhy this is better
Section titled โWhy this is betterโ- Type safety: Maintain type information throughout
- Reusability: One implementation works for many types
- Autocomplete: IDE provides accurate suggestions
- No duplication: Avoid repeating similar code
- Flexibility: Constraints limit to specific type shapes
Key notes / edge cases
Section titled โKey notes / edge casesโ- Type parameters are conventionally named
T,U,V, etc. - Can use
extendsto constrain generic types - Can have default type parameters:
<T = string> - TypeScript can often infer generic types
- Can use
keyofwith generics for type-safe property access
// Default type parameterinterface Config<T = string> { value: T;}
const config1: Config = { value: "default" }; // T defaults to stringconst config2: Config<number> = { value: 42 };
// keyof with genericsfunction getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key];}
const user = { name: "Alice", age: 25 };const name = getProperty(user, "name"); // type: stringconst age = getProperty(user, "age"); // type: numberconst invalid = getProperty(user, "invalid"); // Error!
// Generic array utilitiesfunction first<T>(arr: T[]): T | undefined { return arr[0];}
function last<T>(arr: T[]): T | undefined { return arr[arr.length - 1];}Quick practice
Section titled โQuick practiceโ-
Create a generic function
wrapInArraythat takes a value and returns it in an arrayAnswer
function wrapInArray<T>(value: T): T[] {return [value];} -
Create a generic interface
Pairwith two values of potentially different typesAnswer
interface Pair<T, U> {first: T;second: U;} -
How would you constrain a generic type to objects with a
nameproperty?Answer
function greet<T extends { name: string }>(obj: T): string {return `Hello, ${obj.name}`;}