CodeStaminaBack home

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 does the current program print for the pending payment?
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.