Generics

A generic is a type with parameters. Array<T>, Promise<T> and Map<K, V> work for any element type while keeping that type connected from input to output. You declare type parameters in angle brackets and usually let TypeScript infer the arguments from the call (More on Functions). A parameter can carry a constraint (<K extends keyof T>), a default (<T = string>), or, since TypeScript 5.0, the const modifier, which infers literal types without as const. In a typed event emitter, each instance receives a map of event names to payload types, and every on and emit call is checked against it:

A typed event emitterTypeScript
type Listener<T> = (payload: T) => void;
class Emitter<Events extends Record<string, unknown>> {
  private listeners: { [E in keyof Events]?: Listener<Events[E]>[] } = {};
  on<E extends keyof Events>(event: E, listener: Listener<Events[E]>): void {
    (this.listeners[event] ??= []).push(listener);
  }
  emit<E extends keyof Events>(event: E, payload: Events[E]): void {
    this.listeners[event]?.forEach((listener) => listener(payload));
  }
}
type CartEvents = { added: { sku: string; qty: number }; cleared: undefined };
const cart = new Emitter<CartEvents>();
cart.on("added", (item) => console.log(`${item.qty} x ${item.sku}`));
cart.emit("added", { sku: "TS-BOOK", qty: 2 });
cart.emit("added", { sku: "TS-BOOK" });
cart.on("removed", () => {});
Output
$ npx tsc --noEmit
emitter.ts(17,20): error TS2741: Property 'qty' is missing in type '{ sku: string; }' but
  required in type '{ sku: string; qty: number; }'.
emitter.ts(18,9): error TS2345: Argument of type '"removed"' is not assignable to parameter of
  type 'keyof CartEvents'.
$ node emitter.ts
2 x TS-BOOK
undefined x TS-BOOK

The callback's item needs no annotation: E is inferred as "added", so Events[E] is the payload type. Node.js 2,131 still runs the faulty call and prints undefined, because generics are erased like every other type.