Generics in TypeScript (Part 3)

Introduction

In Part 1, we learned what TypeScript is and why it’s useful. In Part 2, we explored the differences between type and interface. In this part, we’ll cover one of the most powerful features of TypeScript:

Generics

Generics allow you to write reusable, type-safe, and flexible code that works with multiple data types without losing type information.


What Are Generics in TypeScript?

Generics enable you to create components (functions, classes, interfaces) that work with a variety of types rather than a single one.

Instead of hardcoding a type, you use a type variable (commonly T).

Simple Example (Without Generics)

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

❌ Problem: You lose type safety.


Same Example (With Generics)

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

✅ TypeScript now knows exactly what type is returned.


Why Use Generics?

Generics help you:

  • Avoid any
  • Preserve type information
  • Write reusable logic
  • Catch errors at compile time
  • Build scalable and maintainable code

Generic Functions (Real-World Examples)

Example 1: API Response Handler

interface ApiResponse<T> {
  data: T;
  success: boolean;
  message: string;
}

function fetchData<T>(response: ApiResponse<T>): T {
  return response.data;
}

Usage

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

const userResponse: ApiResponse<User> = {
  data: { id: 1, name: "Nishank" },
  success: true,
  message: "Fetched successfully",
};

const user = fetchData(userResponse);

✅ Fully type-safe API handling


Generic Interfaces

Generics are commonly used in interfaces to make them reusable.

Example: Pagination

interface Pagination<T> {
  items: T[];
  total: number;
  page: number;
}

Usage

const usersPage: Pagination<User> = {
  items: [{ id: 1, name: "Nishank" }],
  total: 100,
  page: 1,
};

Generic Classes

Example: Data Store

class Store<T> {
  private data: T[] = [];

  add(item: T) {
    this.data.push(item);
  }

  getAll(): T[] {
    return this.data;
  }
}

Usage

const userStore = new Store<User>();
userStore.add({ id: 1, name: "Nishank" });

const users = userStore.getAll();

Generic Constraints

Sometimes you want to limit what types can be used with a generic.

Example: Object with ID Constraint

interface HasId {
  id: number;
}

function findById<T extends HasId>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id);
}

Multiple Generic Types

function mapValues<K, V>(key: K, value: V) {
  return { key, value };
}

Real-World Use Cases

1. React Component Props

type SelectProps<T> = {
  options: T[];
  onSelect: (value: T) => void;
};

2. Form Handling

interface FormState<T> {
  values: T;
  isValid: boolean;
}

3. Utility Functions

function identity<T>(value: T): T {
  return value;
}

Common Generic Naming Conventions

  • T – Type
  • K – Key
  • V – Value
  • E – Element
  • R – Return type

Generics vs Any

FeatureanyGenerics
Type safety
IntelliSense
Compile-time checks
Reusability

Best Practices

  • Prefer generics over any
  • Keep generics simple and readable
  • Use constraints when required
  • Avoid over-engineering

Conclusion

Generics are a cornerstone of advanced TypeScript development. They allow you to write flexible, reusable, and type-safe code without sacrificing readability.

Mastering generics will significantly improve how you design APIs, utilities, and component libraries—especially in large-scale applications.

In the next part, you can explore TypeScript Utility Types and Advanced Patterns.


Happy coding!

You might also like