TypeScript Fundamentals
Course 1 Β· Chapter 6 Β· Generics Fundamentals
π€ Generics Fundamentals
Generics are TypeScript's way of writing flexible, reusable code that works with many types while maintaining type safety. Imagine a function that works with numbers, strings, objectsβany typeβand still catches type errors. That's generics.
β οΈ The Problem Generics Solve
Without generics, you'd write the same function multiple times for different types, or resort to `any` and lose type safety.
function getFirstString(arr: string[]): string {
return arr[0];
}
function getFirstNumber(arr: number[]): number {
return arr[0];
}
function getFirstAny(arr: any[]): any {
return arr[0];
}
β With `any`, you lose type safety:
const str = getFirstAny(["hello", "world"]);
str.toUpperCase();
str.toReverse();
π Generic Functions
Your First Generic Function
function getFirst<T>(arr: T[]): T {
return arr[0];
}
const firstString = getFirst(["hello", "world"]);
firstString.toUpperCase();
const firstNumber = getFirst([1, 2, 3]);
firstNumber.toFixed(2);
β
Key insight:
<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];
}
const result = pair("hello", 42);
ποΈ 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;
}
}
const stringBox = new Box<string>("hello");
stringBox.getValue();
const numberBox = new Box<number>(42);
numberBox.getValue();
π Generic Constraints
Sometimes you want to restrict what types a generic can accept. For example, "only types that have a `length` property" or "only types that extend a specific interface."
Constraint: Must Have a Property
interface HasLength {
length: number;
}
function getLength<T extends HasLength>(item: T): number {
return item.length;
}
getLength("hello");
getLength([1, 2, 3]);
getLength(42);
Constraint: Must Extend a Type
function merge<T extends object>(obj1: T, obj2: T): T {
return { ...obj1, ...obj2 };
}
merge({ a: 1 }, { b: 2 });
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");
getProperty(person, "email");
π― Common Generic Patterns
Array Methods
function map<T, U>(arr: T[], fn: (T) => U): U[] {
return arr.map(fn);
}
const lengths = map(["a", "bb", "ccc"], (s) => s.length);
Promise Resolution
function fetchData<T>(url: string): Promise<T> {
return fetch(url).then((res) => res.json());
}
interface User {
id: number;
name: string;
}
const userPromise = fetchData<User>("/api/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[] {
}
Goal: Work with strings, numbers, objectsβany type.
β Solution
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 value
set(value) method updates the cache
Goal: Demonstrate generic classes.
β Solution
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) {
}
Goal: Demonstrate generic constraints with `keyof`.
β Solution
π‘ Real-World Tip: Generics Make Your Code DRY
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.