Generics Fundamentals
π€ Generics Fundamentals
β οΈ The Problem Generics Solve
// β Without generics: lots of duplication function getFirstString(arr: string[]): string { return arr[0]; } function getFirstNumber(arr: number[]): number { return arr[0]; } function getFirstAny(arr: any[]): any { return arr[0]; } // Same logic, written 3 times. And if you need boolean, object, etc., write it again!
const str = getFirstAny(["hello", "world"]); // TypeScript thinks str is 'any', so: str.toUpperCase(); // β Works, but TypeScript doesn't know if it's safe str.toReverse(); // β No error, but this method doesn't exist!
π Generic Functions
Your First Generic Function
function getFirst<T>(arr: T[]): T { return arr[0]; } // TypeScript infers the type based on usage: const firstString = getFirst(["hello", "world"]); // T is 'string' // firstString is type 'string', so: firstString.toUpperCase(); // β TypeScript knows it's safe const firstNumber = getFirst([1, 2, 3]); // T is 'number' // firstNumber is type 'number', so: firstNumber.toFixed(2); // β TypeScript knows it's safe
<T> is a type parameter β a placeholder for any type. When you call getFirst([1,2,3]), TypeScript sees the argument is number[], so it sets T = number. The function now behaves as if it were typed specifically for numbers.
Multiple Type Parameters
Functions can have multiple type parameters:
function pair<T, U>(first: T, second: U): [T, U] { return [first, second]; } // Different types for each parameter: const result = pair("hello", 42); // T is 'string', U is 'number' // result is ["hello", 42] with type [string, number]
ποΈ Generic Classes
Classes can be generic too. A common example is a container or wrapper:
class Box<T> { private value: T; constructor(initialValue: T) { this.value = initialValue; } getValue(): T { return this.value; } setValue(newValue: T): void { this.value = newValue; } } // Create a box for strings: const stringBox = new Box<string>("hello"); stringBox.getValue(); // Type: string // Create a box for numbers: const numberBox = new Box<number>(42); numberBox.getValue(); // Type: number
π Generic Constraints
Constraint: Must Have a Property
interface HasLength { length: number; } function getLength<T extends HasLength>(item: T): number { return item.length; } // β Works: strings have length getLength("hello"); // Returns 5 // β Works: arrays have length getLength([1, 2, 3]); // Returns 3 // β Error: numbers don't have length getLength(42); // Error!
Constraint: Must Extend a Type
function merge<T extends object>(obj1: T, obj2: T): T { return { ...obj1, ...obj2 }; } // β Works: objects can be merged merge({ a: 1 }, { b: 2 }); // β Error: strings can't be merged this way merge("hello", "world");
Constraint: Generic Must Be a Key
Ensure a type parameter is a key of another type parameter:
function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; } const person = { name: "Alice", age: 30 }; getProperty(person, "name"); // β "name" is a key of person getProperty(person, "email"); // β "email" is not a key of person
π― Common Generic Patterns
Array Methods
function map<T, U>(arr: T[], fn: (T) => U): U[] { return arr.map(fn); } // Transform strings to numbers: const lengths = map(["a", "bb", "ccc"], (s) => s.length); // Result: [1, 2, 3]
Promise Resolution
function fetchData<T>(url: string): Promise<T> { return fetch(url).then((res) => res.json()); } interface User { id: number; name: string; } // Promise resolves to User: const userPromise = fetchData<User>("/api/user"); // userPromise is Promise<User>
π» Coding Challenges
Challenge 1: Generic Array Reverse
Write a generic function that reverses an array of any type and returns the reversed array. The function should maintain type safety.
function reverse<T>(arr: T[]): T[] { // Your implementation }
Goal: Work with strings, numbers, objectsβany type.
Challenge 2: Generic Cache Class
Create a generic Cache<T> class that stores a value and provides get/set methods. The value can be any type.
- Constructor takes an initial value
get()method returns the cached valueset(value)method updates the cache
Goal: Demonstrate generic classes.
Challenge 3: Generic with Constraint
Write a generic function that accepts an object and a property name, then returns the property value. Use a constraint to ensure the property exists on the object.
function getProp<T, K>(obj: T, key: K) { // Your implementation }
Goal: Demonstrate generic constraints with `keyof`.
If you find yourself writing the same function for different types, or using `any` to make it work with multiple types, generics are your answer. Libraries like React, Vue, Express, and database ORMs use generics extensively. Understanding them now will pay dividends as you read real-world code.
π― What's Next
Generics are powerful, but TypeScript has even more advanced type features. Chapter 7 covers Advanced Types β union types, type guards, discriminated unions, and more sophisticated patterns for keeping your code type-safe.