TypeDrop
2026-08-28 Challenge
2026-08-28
Easy
Typed Notification Dispatcher with Discriminated Unions & Template Literal Channels
You're building the notification service for a project-management app. Users can subscribe to different event channels (email, SMS, push), and each notification type carries its own payload shape. The compiler must guarantee that every notification kind is handled, every channel is valid, and the dispatcher always returns a fully-typed delivery receipt.
Goals
- Define a three-variant discriminated union `Notification` with typed payloads and `kind` literal fields.
- Create `Channel` and `ChannelTag` template-literal types and wire them into the `DeliveryReceipt<N>` generic interface.
- Implement `dispatch` with an exhaustive switch over `notification.kind`, using `assertNever` in the default branch.
- Implement `groupReceiptsByChannel` returning a `Record<Channel, DeliveryReceipt<Notification>[]>` with all three channel keys always present.
challenge.ts
// Key types you must define:
type Channel = "email" | "sms" | "push";
type ChannelTag = `channel:${Channel}`;
// Discriminated union — one variant shown:
type TaskAssignedNotification = {
kind: "task_assigned";
payload: { taskId: string; assigneeId: string };
};
// ... plus "comment_added" and "deadline_approaching"
type Notification = TaskAssignedNotification | /* ... */ never;
// Generic receipt tied to the exact Notification variant:
interface DeliveryReceipt<N extends Notification> {
notificationKind: N["kind"];
channel: Channel;
channelTag: ChannelTag;
sentAt: number;
success: boolean;
}
// Main function you must implement:
function dispatch<N extends Notification>(
notification: N,
channel: Channel
): DeliveryReceipt<N> { /* TODO */ }
Hints (click to reveal)
Hints
- Index into a generic `N extends Notification` with `N["kind"]` to pull the literal type into `DeliveryReceipt` without widening to `string`.
- A template-literal expression like `` `channel:${channel}` `` narrows to `ChannelTag` automatically when `channel` is typed as `Channel` — no cast needed.
- `Record<Channel, DeliveryReceipt<Notification>[]>` forces you to initialise all three keys; seed the accumulator with `{ email: [], sms: [], push: [] }` before reducing.
Useful resources
Or clone locally
git clone -b challenge/2026-08-28 https://github.com/niltonheck/typedrop.git