The last ticket
One prompt. A lot of decisions.

A conversation in ten chapters

The last
ticket.

What happens after
“just build it.”

A small story about the decisions
hiding inside a simple request.

It starts with a simple prompt

Manager

Build a ticket website. Choose a seat, pay, get a ticket.

AI

The checkout works. But two people want the last seat.
What should happen?

A successful checkout shows the ordinary path. Now we have to decide what happens everywhere else.

01 · The reservation

Who gets the
last seat?

Now we need: a reservation

AI

Should selecting a seat hold it, or does the first completed payment win?

A Reserve

Hold during checkout

The buyer has time to pay.

abandoned checkouts block other buyers.

B Race

First payment wins

Unpaid seats stay available.

a buyer can lose the seat while entering payment details.

Manager

Hold it for five minutes.

AvailableHeld · 5 minSold

Now we need expiration rules. And two simultaneous requests must not acquire the same reservation. That’s a correctness requirement once we’ve promised exclusive holds.

Database transactions can help enforce it, but their concurrency behavior matters. For example, PostgreSQL’s serializable transactions can require the application to retry after conflicts.

02 · The timeout

Five minutes.
Then what?

The customer submits payment.

The reservation expires.
No payment response yet.

Payment status: unknown
AI

Should we release the seat?

Manager

If payment failed, yes.

AI

We don’t know whether it failed. The payment may have succeeded, but its response got lost.

“No response” doesn’t mean “nothing happened.”

The system needs an “unknown” state.

Stripe: an operation can succeed even when its response is lost

03 · The policy

Wait longer.
Or let it go.

The payment is unresolved.
The next buyer is waiting.

+2 minA little more time.
Another decision.
0:00 · hold5:007:00 · release

Now we need: a deadline & a remedy

AI

While we’re waiting, which consequence should we accept?

A Wait

Keep holding it

Preserve the buyer’s seat while we find out.

everyone else may wait much longer than five minutes.

B Release

Let someone else buy

The seat is available again.

a late successful payment may need a refund.

Manager

Give pending payments two extra minutes. Then release the seat and refund any payment that succeeds later.

A reasonable policy. Now payment confirmation, reservation expiry, and refunds need precise rules—even when their updates arrive in a different order.

The words “five-minute reservation” did not specify any of that.

04 · The retry

“Can’t we just
try again?”

First requestpay_042
Retrypay_042
1 payment attempt. One charge.

Now we need: idempotency

Manager

If we don’t get a response, retry the payment.

AI

Yes—but the retry must identify the same payment attempt. Otherwise we could charge the customer twice.

Payment APIs can support this through idempotency: repeat an operation using the same identifier without repeating its effect. Stripe describes this for safely retrying requests after network failures.

Manager

Fine. Use that.

AI

Then we must preserve that identifier across retries and server restarts, associate it with the correct order, and distinguish a retry from a genuinely new purchase attempt. We still need the reservation policy while payment status is unknown.

The manager started with a button. Now they’re reasoning about which actions count as the same action, what survives a crash, and what the system knows.

05 · The backup

When is it
“confirmed”?

MainA12
copy purchase
BackupA12

Now we need: a durability promise

Manager

Purchases must survive a server failure. Add a backup database.

AI

Do we say “confirmed” before or after the backup has saved the purchase?

A Before

Confirm, then copy

The primary saves it; no waiting for the backup.

a failure can leave the backup missing a confirmed purchase.

B After

Copy, then confirm

The required backup has also saved it.

confirmation waits if that backup can’t be reached.

These are actual replication trade-offs. PostgreSQL documents both the potential loss of recent transactions with asynchronous replication and the additional wait involved in synchronous replication. More replicas can improve availability, with extra resources and configuration.

Manager

Wait for the backup. We can’t lose confirmed purchases.

That decision changes checkout latency, outage behavior, infrastructure cost, and the meaning of “confirmed.”

06 · The failover

Silent doesn’t
mean stopped.

Main server
A12

Could sell to Alice

Backup
A12

Could sell to Ben

Now we need: one authority

Manager

If the main server stops responding, switch to the backup.

AI

What if it’s still running, and only the connection broke? If both accept sales independently, both could sell A12.

Manager

Then make sure only one is allowed to sell.

Now the implementation needs a reliable way to establish which server has authority, and prevent the other from continuing to accept purchases.

An established database or managed service can supply that machinery. Its guarantees and failure conditions still determine what your application can promise.

“How do we know the old server has stopped accepting purchases before the replacement starts?” That is a distributed-systems engineering question. It emerged directly from “sell tickets reliably.”

07 · The system

Every answer
changes
the system.

Every request sounds reasonable. Each answer changes the requirements elsewhere.

  1. 01
    Reservations change inventoryHolding A12 for one buyer makes everyone else wait.
  2. 02
    Deadlines create refund obligationsRelease the seat, and a late payment may arrive after it’s resold.
  3. 03
    Retries depend on durable identityThe same payment attempt must remain the same after a crash.
  4. 04
    Replication changes confirmation timeWaiting for a durable copy affects checkout and outage behavior.
  5. 05
    Failover must preserve exclusivityReplacing a server must keep the original “one seat, one owner” promise.

The deeper you go, the more you’re doing engineering.

Those are engineering concepts, even when discussed without technical vocabulary.

08 · The engineering

From choosing
to evaluating.

You can delegate technical decisions. Independently evaluating them takes engineering knowledge.

The responsibility behind the code

The distinction is the ability to evaluate and take responsibility for the system’s behavior.

  1. 01
    Which guarantees hold, under which failures?Does “one owner” survive a timeout, a restart, or a broken connection?
  2. 02
    How do the parts interact?Does payment recovery still respect the reservation deadline?
  3. 03
    What evidence demonstrates correctness?What shows that simultaneous purchases and late confirmations are handled correctly?

A developer should translate consequential trade-offs into business consequences. The manager needn’t select every mechanism.

When you start evaluating those guarantees, interactions, and evidence yourself, you’re taking on software engineering—even if an AI writes every line.

09 · The point

A short prompt
leaves decisions
unspecified.

Those decisions
still get made.

Engineering is understanding which ones matter and establishing that the resulting system behaves as intended.

What does it promise?
What happens when it fails?
How do you know it works?

A little further reading