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.
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.
Useful resources
Or clone locally
git clone -b challenge/2026-08-31 https://github.com/niltonheck/typedrop.git