Building Waitlist: What a Restaurant POS Taught Me About Real Software

Building Waitlist: What a Restaurant POS Taught Me About Real Software

Projects10 min readOctober 4, 2026

The story behind building a restaurant queue and POS system, and why connected workflows are harder than individual features.

The first version of Waitlist looked like many software projects I had built before: a collection of screens that worked individually.

Then I tried to imagine a restaurant using it during a busy service.

That changed everything.

A restaurant does not care that a table component is beautifully designed. It cares that when a customer arrives, the queue is correct, the captain knows what to do, the kitchen receives the order, the printer prints the KOT, the table state is accurate, the bill is correct, the payment is recorded, and the invoice reaches the customer.

That became the real problem I was solving.

From queue management to an operational system

Waitlist started around restaurant waiting queues, but the product grew into a broader restaurant workflow.

The journey became something like:

Customer → Queue → Table → Captain → Order → KOT → Kitchen → Billing → Payment → Invoice

Every arrow mattered.

If the queue said a customer was waiting while the table was already occupied, the system was wrong.

If the captain app showed a table as available while the desktop admin had assigned it, the system was wrong.

If an order was created but the KOT did not reach the kitchen, the system was wrong.

If a bill was generated but the payment state did not update, the system was wrong.

This was my first serious lesson in workflow design:

Software quality is often determined between features, not inside features.

The printer problem nobody puts in a mockup

One of the most educational parts of Waitlist was printing.

In a design file, printing is one line: “Print KOT.”

In a real system, printing becomes an infrastructure problem.

A network printer may listen on a particular port. A desktop application may need native access. A browser cannot always perform the same operations as a local application. Printer configuration can differ between environments. A print bridge can be unavailable. A job can be queued while the UI assumes it is already printed.

I ended up thinking about printer jobs as their own system instead of treating printing as a button click.

That shift was important.

The UI should create a print request. A reliable process should pick it up. The system should know whether it is pending, processing, completed, or failed.

The moment I treated printing as a workflow instead of a side effect, the architecture became clearer.

Desktop, mobile, and web made state harder

Waitlist also involved a desktop admin application, a captain mobile app, and web-based flows.

That sounds impressive until three clients need to agree on one table state.

A table can be free, pending an order, occupied, or in billing. When one client changes the state, every other client eventually needs to understand that change.

This introduced questions I had previously underestimated:

  • Who owns the state?
  • Which operation is authoritative?
  • What happens when two clients update the same record?
  • How quickly should another device see the change?
  • What happens when a request fails halfway through?
  • Is polling good enough, or should the system use realtime events?

These are not UI questions.

They are distributed-state questions hiding inside a restaurant application.

The painful bugs were the valuable ones

Waitlist had its share of bugs: deployment issues, TypeScript errors, printer configuration problems, mobile build failures, notification setup issues, database policy mistakes, and payment integration experiments.

Some errors were embarrassing because they appeared during demos or testing rather than development.

But they changed how I work.

I learned not to call something “done” because the happy path works.

A workflow is not done until I have considered:

  • empty state
  • loading state
  • failure state
  • duplicate action
  • slow network
  • stale data
  • permissions
  • device differences
  • recovery after failure

That checklist sounds boring.

Boring is good.

Production software is mostly boring decisions made correctly.

Why the payment decision changed

The subscription/payment direction also evolved during the project.

We experimented with Razorpay, ran into test-mode issues, and eventually simplified the product around fixed yearly plans with UPI-based payment and manual verification through the superadmin workflow.

This was a useful product lesson: the technically most sophisticated option is not automatically the best option.

If the customer understands UPI and the operational team can verify payments reliably, a simpler workflow can be more appropriate than adding a complicated payment system just because it looks more “professional.”

Engineering is partly knowing what not to build.

What I would do differently

If I started Waitlist again, I would define the state machine earlier.

I would write down every major entity and transition before building the UI:

  • customer enters queue
  • customer leaves queue
  • table assigned
  • order created
  • order sent to kitchen
  • KOT printed
  • order served
  • billing started
  • payment completed
  • table released

Then I would design the UI around those transitions.

I would also invest earlier in end-to-end testing instead of discovering integration bugs late.

The biggest lesson

Waitlist taught me that a serious product is not “a website with lots of features.”

It is a system that has to remain coherent while people, devices, printers, databases, and business rules interact with each other.

That is a very different way of thinking.

I started the project wanting to build a restaurant application.

I came out of it understanding workflows, state, failure recovery, native integrations, realtime requirements, and the uncomfortable distance between a demo and a product.

And that distance is where most of the real engineering lives.