Skip to contentPedro Farbo
Lesson 5 / 525 min

Introduction to Generics

Introduction to Generics

Generics allow you to create components that work with various types while maintaining type safety. They're like "templates" that you can customize.

The Problem

Imagine you want to create a function that returns the first element of an array:

typescript
// Without generics - loses type informationfunction firstElement(arr: any[]): any {  return arr[0];} const num = firstElement([1, 2, 3]);    // type: anyconst str = firstElement(["a", "b"]);   // type: any // We lost the information that num is number and str is string!

The Solution: Generics

typescript
function firstElement<T>(arr: T[]): T {  return arr[0];} const num = firstElement([1, 2, 3]);    // type: numberconst str = firstElement(["a", "b"]);   // type: string // TypeScript correctly infers the type!

The T is a type parameter - a placeholder that will be replaced by the actual type when the function is called.

Basic Syntax

typescript
// Generic functionfunction identity<T>(value: T): T {  return value;} // Calling - TypeScript infers the typeidentity("text");  // T = stringidentity(42);      // T = number // Or specifying explicitlyidentity<boolean>(true);

Multiple Type Parameters

typescript
function pair<K, V>(key: K, value: V): [K, V] {  return [key, value];} const result = pair("age", 30); // [string, number]const other = pair(1, true);    // [number, boolean]

Generic Interfaces

typescript
interface Box<T> {  content: T;} const textBox: Box<string> = {  content: "Hello!"}; const numberBox: Box<number> = {  content: 42};

Generic Classes

typescript
class 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];  }} const numberStack = new Stack<number>();numberStack.push(1);numberStack.push(2);console.log(numberStack.pop()); // 2 const stringStack = new Stack<string>();stringStack.push("a");stringStack.push("b");

Constraints

Limit which types can be used with extends:

typescript
// T must have the 'length' propertyinterface HasLength {  length: number;} function getLength<T extends HasLength>(item: T): number {  return item.length;} getLength("hello");     // OK - string has lengthgetLength([1, 2, 3]);   // OK - array has lengthgetLength({ length: 10 }); // OK - object has length getLength(42);          // Error - number doesn't have length

Using Type Parameters in Constraints

typescript
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {  return obj[key];} const person = { name: "Pedro", age: 30 }; getProperty(person, "name"); // OK, returns stringgetProperty(person, "age");  // OK, returns numbergetProperty(person, "email"); // Error - 'email' doesn't exist

Default Type Parameters

typescript
interface Response<T = any> {  data: T;  status: number;} // Uses default type (any)const response1: Response = {  data: "anything",  status: 200}; // Specifies the typeconst response2: Response<User> = {  data: { name: "Pedro", age: 30 },  status: 200};

Practical Example: API Function

typescript
async function fetchData<T>(url: string): Promise<T> {  const response = await fetch(url);  return response.json();} interface User {  id: number;  name: string;} // TypeScript knows the return typeconst user = await fetchData<User>("/api/user/1");console.log(user.name); // Autocomplete works!

Conclusion

Generics are a powerful tool for creating flexible, type-safe code. With them, you can write reusable components without sacrificing the benefits of static typing.

Enjoyed the content? Your contribution helps keep everything online and free!

PIX:0737160d-e98f-4a65-8392-5dba70e7ff3e