When Google Forms stops scaling, build the boring real-time system
Spreadsheets break the moment an event goes live. I designed, built, and coordinated a real-time registration system for TEDxUniversityOfCrete — treating registration as state, not rows, and leading a junior team through every phase.
For most events, Google Forms plus a spreadsheet is the right answer — until registration goes live. People sign up, change their mind, mistype their email, and show up for a workshop they never confirmed, all at once. A form is a write-only funnel; an event is a system with state. When I took ownership of workshop registration for TEDxUniversityOfCrete, my job was to close that gap and lead a small team of junior developers from first sketch to event day.

Three failures a form cannot see
A wrong email means the confirmation never arrives — you find out when they are at the door. A silent no-show keeps a seat occupied in the data while the waitlist never moves. And the moment two volunteers open the same sheet, the number on screen is already wrong. These are not bugs you fix with stricter fields; the system simply has no place to hold them.
Three steps, no manual
Before anyone signs in, the flow explains itself: rank your workshops, we assign the seats, you get an email with the result. Onboarding that reads in five seconds is how you keep a few hundred students from defaulting back to the comfort of a form.

Stop modeling rows, start modeling state
The fix was to model a registration as an object with a lifecycle — pending_confirmation, confirmed, waitlisted, cancelled — not a line of text. Every failure above is a missing transition. A bad email never leaves pending_confirmation. A cancellation fires waitlist promotion. Capacity becomes a database constraint, so two people can't take the last seat at once.
I kept the stack deliberately plain — a straightforward backend, tokenized links instead of logins, QR check-in over email — because three to four junior part-time developers had to build and maintain it with me. Every architecture call was weighed against whether they could own their piece. Boring was a feature.

Every submission ends with a clear receipt, so the attendee sees exactly what went on record and what happens next — no guessing whether the form "went through."

Design the handoffs first
The hard part of leading the build was getting the team to wire up the failures before the happy path: bounced confirmations, late cancellations, full workshops, duplicate scans. I split each transition into a clean, ownable task so a developer could take it end to end across our three repos. Coordinating meant holding the scope line, keeping the data model consistent, and making sure a part-time team always had an unambiguous next step — every phase, from requirements to deployment on the university server.

What you get back
A single source of truth that updates in real time, a one-second check-in, and an admin view where the number on screen is the number in the room.

The spreadsheet was never the problem. Treating a live, stateful process as a static list — and the build as a solo sprint instead of a coordinated one — was. Name the states, design the transitions, give each person a piece they can own, and the quiet failures stop being quiet.