GUIDED PREVIEW
Make every payment state explicit.
Read the complete program, compare prepared repairs and check each choice against four requirements.
- Skill
- Type narrowing
- Time
- 8–12 minutes
- Format
- Guided choices
- Access
- No sign-in
01 / Predict the current behavior
The goal: turn each payment result into a useful message. A payment can be paid, declined, or pending.
The status field identifies which shape we have. TypeScript then knows which other fields are safe to read: only paid results have receiptId, and only declined results have reason.
type PaymentResult =
| { status: 'paid'; receiptId: string }
| { status: 'declined'; reason: string }
| { status: 'pending' };
function assertNever(value: never): never {
throw new Error('Unhandled state: ' + JSON.stringify(value));
}
function describePayment(result: PaymentResult): string {
switch (result.status) {
case 'paid':
return 'Receipt: ' + result.receiptId;
case 'declined':
return 'Declined: ' + result.reason;
default:
return 'Unknown payment status';
}
}
console.log(describePayment({
status: 'paid', receiptId: 'rcpt_42'
}));
console.log(describePayment({
status: 'declined', reason: 'Card expired'
}));
console.log(describePayment({ status: 'pending' }));What is a discriminated union?
It is a union of object types that share one literal field—in this case, status. Checking that field narrows the object to one exact shape, so TypeScript can safely expose receiptId or reason.