Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Generics Basics in TypeScript

Generics Basics

~17 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

Our Library class can so far ONLY manage Book objects – what if we want to reuse the SAME pattern (managing stock, searching, checking out) for audiobooks, DVDs, or magazines too, WITHOUT duplicating the class? Generics are the answer: "type parameters" that make a class/function reusable for ANY type.

The problem without generics

function firstElement(list: number[]): number {
  return list[0];
}

function firstElementString(list: string[]): string {
  return list[0];
}
// A separate, near-identical function for EVERY type? That doesn't scale.

The generic solution: a type parameter

function firstElement<T>(list: T[]): T {
  return list[0];
}

firstElement<number>([1, 2, 3]);        // T becomes number
firstElement<string>(['a', 'b']);       // T becomes string
firstElement([true, false]);              // T is AUTOMATICALLY inferred as boolean -
                                           // the angle brackets are usually OMITTABLE

<T> is a PLACEHOLDER for "some type, determined at the actual call site" – T is pure convention (stands for "Type"), any valid identifier works. The DECISIVE advantage over any: TypeScript knows that firstElement([1, 2, 3])'s result is number, NOT any – full type safety is preserved, just generic instead of hardcoded.

In practice: a generic repository

Let's build a generic Repository<T> class that provides the SHARED pattern (managing a list, searching, adding) for ANY medium type:

src/repository/Repository.ts
export class Repository<T> {
  protected items: T[];

  constructor(initialItems: T[] = []) {
    this.items = initialItems;
  }

  all(): readonly T[] {
    return this.items;
  }

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

  count(): number {
    return this.items.length;
  }

  find(predicate: (item: T) => boolean): T | undefined {
    return this.items.find(predicate);
  }
}
import { Repository } from './repository/Repository.js';
import { Book } from './models/Book.js';
import { Audiobook } from './models/Audiobook.js';

const bookRepository = new Repository<Book>();
bookRepository.add(new Book(/* ... */));

const audiobookRepository = new Repository<Audiobook>();
audiobookRepository.add(new Audiobook(/* ... */));

// ONE class, TWO fully type-safe, independent usages -
// bookRepository.find(...) returns Book | undefined, NOT Audiobook | undefined

Repository<Book> and Repository<Audiobook> are two DIFFERENT, specialized versions of the SAME generic class – TypeScript keeps them SEPARATE: bookRepository.find(...) is guaranteed to return a Book | undefined, never an Audiobook.

Generics in functions vs. classes

// Generic function - T is determined FRESH PER CALL:
function lastElement<T>(list: T[]): T | undefined {
  return list[list.length - 1];
}

// Generic class - T is fixed ONCE at new, applies to the ENTIRE instance:
const repo = new Repository<Book>(); // T = Book FOREVER, for THIS repo instance

Achtung: A common mix-up: with generic FUNCTIONS, T can differ on EVERY call; with generic CLASSES, T gets fixed ONCE at instantiation (new Repository<Book>()) and stays UNCHANGEABLE for that one instance – a second call to add() can NOT suddenly accept a different type.

Tipp: Rule of thumb: as soon as you're WRITING code that would work fine with any, but you actually WANT to keep type safety ("the result should have the same type as the input"), that's a strong signal for generics. any discards type information ENTIRELY, generics preserve it – just FLEXIBLY instead of hardcoded.