Go Table Talk
ArchivedApril 1, 2022
A Go backend for a mobile card game platform — play card games with friends on your phone. Works locally, never deployed. A hard look at the three chasms of backend development: building it locally, deploying it for real, and scaling it under load. Plus the brutal reality of testing multiplayer as a solo developer.
Purpose
Built a Go API to serve as the backend for a series of card games playable on mobile with friends. The API handled game state, turns, card dealing, and player sessions. It worked on localhost. That is where it stayed.
Stack
What I Learned
- There are three distinct chasms in backend development, and most solo developers only cross the first: (1) Building locally — your API handles requests on localhost, returns correct responses, game logic works, state persists. This is where confidence is highest and reality is most forgiving. (2) Deploying — your API now runs on a real server with a real domain, real SSL, real firewalls, real uptime expectations, and real users whose connections drop, whose requests arrive out of order, and whose clients are versions you have not tested. (3) Scaling — your API handles not one game but a hundred concurrent games, with state isolation, connection pooling, database contention, and memory pressure. Each chasm is wider than the last.
- The localhost trap is insidious because everything works perfectly. Latency is zero. The network never drops. There is only one client. The database is local. The environment variables are set. Deployment shatters every one of these assumptions simultaneously: latency is real, networks are unreliable, multiple clients interact, the database is remote, and you forgot to set half the env vars. The gap between "works on my machine" and "works in production" is not a gap — it is a canyon.
- Testing multiplayer as a solo developer is one of the hardest practical problems in indie game development. A card game needs at least two players. To test, you need two clients connected to the same server, taking turns, seeing each other's actions in real time. Options: (a) run multiple simulator instances side by side (works but is clunky), (b) write bot clients that simulate player behavior (works but you are testing against your own assumptions), (c) recruit friends to test (works but you cannot iterate at 2am), (d) write comprehensive integration tests that simulate multi-client sessions programmatically (works and is the right answer, but requires deep dedication to testing infrastructure).
- The card game domain (shuffling, dealing, hand management, turn order, win conditions) is a clean state machine exercise. A game of poker: Waiting → Dealing → Betting Round 1 → Flop → Betting Round 2 → Turn → Betting Round 3 → River → Betting Round 4 → Showdown → Payout → Next Hand. Each state transition has rules, each player has a perspective (they see their own cards but not others), and the server must be authoritative about the deck state to prevent cheating. It is a beautiful engineering problem.
- Go was a natural choice for this because card game servers are I/O bound (waiting for player input) with occasional CPU bursts (evaluating hands, shuffling). Goroutines handle the waiting efficiently — one goroutine per player connection, all blocked on reads most of the time, consuming almost no resources. When a player acts, the game logic runs, state updates, and responses fan out to all connected clients. The concurrency model maps perfectly to turn-based multiplayer.
Key Insights
- The three-chasm model (build → deploy → scale) is the most useful mental framework for understanding why so many side projects die on localhost. Most developers are skilled enough to build. Fewer are practiced enough to deploy. Almost none have scaled. The industry selects for people who have crossed all three, which is why production experience is valued so highly — it is evidence that you have faced the canyons and crossed them.
- Solo testing of multiplayer systems is solvable but requires treating test infrastructure as a first-class product. The teams that ship multiplayer games write bot clients, replay systems (record a real session, replay it against new code), deterministic game simulations (same seed → same outcome, every time), and headless server tests that spin up N virtual players and run thousands of games. This is not test code — this is test infrastructure, and it often represents more engineering effort than the game itself.
- The card game concept lives on in Hot Potato Games. Table Talk was trying to solve the same problem that every mobile card game faces: how do you make playing cards with friends as easy as opening an app? The technical stack has evolved (Flutter/Flame instead of a separate Go backend), but the design question is unchanged. Making multiplayer frictionless is still the unsolved problem.
- If I could go back and give myself one piece of advice during this era, it would be: deploy first, build features second. Spin up a server, deploy a "hello world" API, connect a client to it over the real internet, and confirm the pipeline works end to end. Then add game logic. Building a full card game engine on localhost and then trying to deploy it is backwards — you discover deployment problems after you have maximized the blast radius of those problems.
This post was composed through a conversation between Brett Owers and Claude Code (Anthropic). The content reflects Brett's recollection of each project and the lessons drawn from it. Some details may be approximate or omitted — the purpose is to paint an honest picture of a software engineer's development over time, not to serve as a precise historical record.