Intermediate Exercism • typescript

The Magic of Generics

Lesson Overview

Learn how to write reusable, type-safe components using TypeScript Generics.

The Magic of Generics

Generics are one of the most powerful features of TypeScript. They allow you to create components (functions, classes, or interfaces) that work with a variety of types while still maintaining full type safety.

The Problem

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

function getFirstElement(arr: any[]): any {
  return arr[0];
}

const numbers = [1, 2, 3];
const firstNum = getFirstElement(numbers); // TypeScript thinks firstNum is 'any'

By using any, we lose all type information. We want a way to say: “This function takes an array of some type, and it returns an element of that same type.”

The Solution: Generics

We can use a type variable (usually denoted as <T>) to capture the type provided by the user.

function getFirstElement<T>(arr: T[]): T {
  return arr[0];
}

const numbers = [1, 2, 3];
const firstNum = getFirstElement(numbers); // TypeScript correctly infers firstNum is 'number'

const names = ["Alice", "Bob"];
const firstName = getFirstElement(names); // TypeScript correctly infers firstName is 'string'

Generic Interfaces

You can also use generics with interfaces to create flexible data structures.

interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

interface User {
  id: number;
  name: string;
}

const userResponse: ApiResponse<User> = {
  data: { id: 1, name: "Alice" },
  status: 200,
  message: "Success"
};

Generic Constraints

Sometimes you want to limit what types can be passed to a generic. You can use the extends keyword to add a constraint.

interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(item: T): void {
  console.log(item.length);
}

logLength("Hello"); // OK: String has length
logLength([1, 2, 3]); // OK: Array has length
logLength(123); // Error: Number does not have a 'length' property.

Summary

Generics allow you to:

  1. Avoid code duplication: One function can work for many types.
  2. Maintain Type Safety: Unlike any, generics preserve the actual type of your data.
  3. Build Flexible APIs: Perfect for utility functions, API responses, and reusable components.