TypeDrop

A new TypeScript challenge every day. Sharpen your types.

TypeDrop delivers a fresh TypeScript challenge every day, generated by AI. Pick a challenge, open it in StackBlitz (preferred) or CodeSandbox (or clone it locally), and make the tests pass. No accounts, no setup — just you and the type system.

Learn more on GitHub →

2026-08-31 Easy

Typed In-Memory Event Emitter with Listener Registry

You're building the event bus for a real-time dashboard application. UI components subscribe to typed events (user login, metric update, alert fired), and the compiler must guarantee that every listener receives exactly the right payload shape — no casting, no guessing.

Goals

  • Define AppEventMap with three precisely typed event entries including a union-typed severity field.
  • Create the ListenerRegistry mapped type so each event key maps to the correct array of typed listeners.
  • Implement on(), off(), emit(), and listenerCount() using generic constraints so the compiler enforces payload shapes at every call site.
  • Initialise the registry in the constructor so every event slot starts as an empty array without using `any`.
challenge.ts
// Key types you must define and use:

export type AppEventMap = {
  "user:login":    { userId: string; timestamp: number };
  "metric:update": { metricId: string; value: number; unit: string };
  "alert:fired":   { alertId: string; severity: "low" | "medium" | "high"; message: string };
};

export type Listener<E extends keyof AppEventMap> = (
  payload: AppEventMap[E]
) => void;

// Helper registry — maps every event name to its listener array:
type ListenerRegistry = { [E in keyof AppEventMap]: Listener<E>[] };

export class EventEmitter {
  private registry: ListenerRegistry; // no `any` allowed!

  on<E extends keyof AppEventMap>(event: E, listener: Listener<E>): void { /* TODO */ }
  off<E extends keyof AppEventMap>(event: E, listener: Listener<E>): void { /* TODO */ }
  emit<E extends keyof AppEventMap>(event: E, payload: AppEventMap[E]): void { /* TODO */ }
  listenerCount<E extends keyof AppEventMap>(event: E): number { /* TODO */ return 0; }
}
Hints (click to reveal)

Hints

  • The ListenerRegistry mapped type looks like `{ [E in keyof AppEventMap]: Listener<E>[] }` — each slot's element type depends on its own key.
  • To seed the registry in the constructor without `any`, cast the accumulator through `as ListenerRegistry` only once after building it with `Object.fromEntries` — or simply assign each key manually.
  • Inside off(), use `Array.prototype.indexOf` or `.filter` to remove the exact listener reference; remember that function identity matters, not deep equality.

Or clone locally

git clone -b challenge/2026-08-31 https://github.com/niltonheck/typedrop.git