Type-Safe emit and on
Enforce correct payloads when emitting and listening.
Type-Safe emit and on is a free TypeScript Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
A Type-Safe emit Signature
Make emit generic over the event name K. The payload parameter is then Events[K], so name and payload must match.
interface Events { login: { userId: string }; logout: void }
function emit<K extends keyof Events>(event: K, payload: Events[K]) {
console.log("emit", event, JSON.stringify(payload));
}
emit("login", { userId: "u1" });Wrong Payload Is a Compile Error
If the payload does not match the event name, TypeScript rejects it. The generic links the two parameters.
interface Events { login: { userId: string } }
function emit<K extends keyof Events>(event: K, payload: Events[K]) {}
// emit("login", { id: "u1" }); // Error: id is not userId
emit("login", { userId: "u1" });
console.log("ok");Unknown Event Names Are Rejected
Because K extends keyof Events, you cannot emit an event that is not in the map.
interface Events { login: { userId: string } }
function emit<K extends keyof Events>(event: K, payload: Events[K]) {}
// emit("signup", { userId: "x" }); // Error: "signup" not a key
emit("login", { userId: "x" });
console.log("only known events");Typed Handlers With on
The on method registers a handler whose argument is exactly Events[K], so handlers receive correctly typed payloads.
interface Events { login: { userId: string }; logout: void }
function on<K extends keyof Events>(event: K, handler: (payload: Events[K]) => void) {
console.log("subscribed to", event);
}
on("login", (p) => console.log(p.userId));Handler Argument Is Inferred
You do not annotate the handler parameter; TypeScript infers it from the event name, giving full autocomplete inside the handler body.
interface Events { message: { text: string } }
function on<K extends keyof Events>(event: K, handler: (p: Events[K]) => void) {}
on("message", (p) => {
// p is { text: string }
console.log(p.text.toUpperCase());
});Handling Payloadless Events
For void events, the handler simply takes no useful argument. You can call its handler without data.
interface Events { logout: void }
function on<K extends keyof Events>(event: K, handler: (p: Events[K]) => void) {}
on("logout", () => console.log("logged out"));Combining emit and on
Together, emit and on form a typed pub/sub. Emitting an event delivers a correctly typed payload to its handlers.
interface Events { ping: { at: number } }
type Handler<K extends keyof Events> = (p: Events[K]) => void;
const handlers: { ping: Handler<"ping">[] } = { ping: [] };
function on<K extends keyof Events>(e: K, h: Handler<K>) { handlers[e].push(h as Handler<"ping">); }
function emit<K extends keyof Events>(e: K, p: Events[K]) { handlers[e].forEach(h => h(p as Events["ping"])); }
on("ping", p => console.log("at", p.at));
emit("ping", { at: 5 });Constraining the Payload Position
The generic ensures the payload argument is checked against the chosen event, catching mismatches at the call site, not deep in a handler.
interface Events { add: { x: number; y: number } }
function emit<K extends keyof Events>(e: K, p: Events[K]) {
return e;
}
emit("add", { x: 1, y: 2 });
// emit("add", { x: 1 }); // Error: y missing
console.log("payload checked");Autocomplete for Event Names
Because the first parameter is keyof Events, editors autocomplete valid event names as you type the call.
interface Events { open: void; close: void }
function emit<K extends keyof Events>(e: K, p: Events[K]) {}
// Typing emit(" suggests "open" and "close"
emit("open", undefined as void);
console.log("autocompleted names");Why Generics Are Essential Here
Without a generic, you would need overloads for every event. One generic signature scales to any number of events automatically.
interface Events { a: { x: number }; b: { y: string } }
function emit<K extends keyof Events>(e: K, p: Events[K]) { return p; }
console.log(emit("a", { x: 1 }), emit("b", { y: "hi" }));A Reusable Pattern
This emit<K>(event, payload) and on<K>(event, handler) pattern is the core of every type-safe emitter, which we will package into a class next.
interface Events { tick: { n: number } }
function emit<K extends keyof Events>(e: K, p: Events[K]) { return e; }
function on<K extends keyof Events>(e: K, h: (p: Events[K]) => void) { return e; }
console.log(emit("tick", { n: 1 }), on("tick", p => p.n));Quick Check: Typed emit and on
Test your understanding of type-safe emit and on.
Recap: Type-Safe emit and on
You built generic emit and on signatures where the payload is Events[K], so event names autocomplete, wrong payloads are compile errors, and handler arguments are inferred.
interface Events { login: { userId: string } }
function emit<K extends keyof Events>(e: K, p: Events[K]) { return e; }
console.log(emit("login", { userId: "u1" }));Frequently asked questions
Is the “Type-Safe emit and on” lesson free?
Yes — the full text of “Type-Safe emit and on” is free to read here on the web, and the TypeScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO.
What will I learn in “Type-Safe emit and on”?
Enforce correct payloads when emitting and listening. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start TypeScript Academy?
No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Type-Safe emit and on” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this TypeScript Academy lesson?
Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Typing Event Maps
- Type-Safe emit and on
- Generic Event Emitter Class
- Inferring Listener Arguments