Building Real-Time Multiplayer with Flutter and Firebase
How I built a competitive number guessing game with sub-second sync, ELO ranking, and a virtual economy — all on Firebase's free tier.

When I set out to build Number Strike Baseball, I knew the core challenge wasn't the game logic — it was making two players feel like they were in the same room. Real-time multiplayer on mobile demands sub-second sync, graceful disconnection handling, and cheat prevention, all while keeping infrastructure costs near zero.
Before settling on Firestore, I evaluated several real-time database options including Supabase Realtime, Socket.io with a custom backend, and Firebase Realtime Database. Supabase offered PostgreSQL familiarity but lacked the seamless offline support critical for mobile gaming. Socket.io gave full control but meant managing WebSocket infrastructure — a non-starter for a solo developer. Firebase Realtime Database was a contender, but Firestore's document model mapped more naturally to game sessions with nested turn data and player states.
“How I built a competitive number guessing game with sub-second sync, ELO ranking, and a virtual economy — all on Firebase's free tier.”
Firestore's real-time listeners turned out to be the perfect fit. Each game session lives as a single document with nested turn data. Both clients subscribe to the same document, and Firestore's optimistic updates mean the UI responds instantly while the server confirms the write.
Handling disconnections gracefully was crucial for a competitive game. If a player loses connectivity mid-match, the game needs to distinguish between a temporary network blip and an intentional abandonment. I implemented a three-tier presence system: Firestore's onDisconnect handler marks the player as 'disconnected' immediately, a Cloud Function checks after 30 seconds and pauses the game timer, and after 60 seconds of continuous disconnection the match is forfeited. This prevents players from stalling games they're losing while protecting those with genuinely unstable connections.
The matchmaking system runs entirely in Cloud Functions. When a player queues up, a function scans the waiting pool for opponents within a 200-point ELO range. If no match is found within 10 seconds, the range widens by 100 points every 5 seconds. This balances match quality against wait time.
The ELO calculation itself runs within the matchmaking Cloud Function to ensure atomic rating updates. When a game concludes, the function reads both players' current ratings, computes the expected win probability using the standard ELO formula, applies a K-factor that varies based on the player's total games played, and writes both updated ratings in a single Firestore batch write. This atomicity prevents rating drift that could occur if one player's update succeeded but the other's failed due to a transient error.
Cheat prevention was non-negotiable for ranked play. All game state validation happens server-side in Cloud Functions. The client sends a guess, the function validates it against the secret number, computes strikes and balls, and writes the result back. The client never knows the opponent's number.
Latency optimization became an obsession once competitive play launched. Every millisecond matters when players are racing to guess numbers. I moved from standard HTTPS-triggered Cloud Functions to callable functions, which eliminated the overhead of REST API parsing. Client-side, I implemented optimistic UI updates for non-critical interactions — the interface responds immediately while the server confirmation arrives asynchronously. For game state updates, I used Firestore's snapshot listeners with metadata to distinguish between local cache updates and server-confirmed writes, giving players visual feedback about synchronization status.
The virtual economy uses Firestore transactions to prevent race conditions. When a player purchases an item, a transaction reads their balance, checks the price, deducts the amount, and adds the item — all atomically. This prevents double-spending even under concurrent requests.
Player analytics drove significant product decisions throughout development. I built a lightweight telemetry system that tracks game outcomes, session durations, matchmaking wait times, and virtual economy transactions in BigQuery via Cloud Functions. This pipeline processes roughly 50,000 events per day and powers a dashboard that shows real-time player engagement metrics. The most actionable insight was discovery of a drop-off cliff at the 5th game — players who completed 5 matches had an 80% chance of returning the next day, which led us to redesign the new player experience to lower friction in those first critical sessions.
One unexpected challenge: Cloud Functions cold starts. During low-traffic periods, the first matchmaking request can take 2-3 seconds while the function container spins up. I mitigated this with a scheduled keep-alive ping every 5 minutes, which keeps at least one instance warm.
Looking back, the architecture decisions that mattered most weren't about technology — they were about knowing where to draw the boundary between client and server responsibility. Everything related to game integrity runs server-side: matchmaking, state validation, ELO calculations, and economy transactions. Everything related to user experience runs client-side: animations, optimistic updates, and local caching. This separation made the system both secure and responsive. If I were starting over, I'd invest earlier in automated integration testing for the Cloud Functions — manual testing of multiplayer scenarios was the biggest time sink during development.
When I set out to build Number Strike Baseball, I knew the core challenge wasn't the game logic — it was making two players feel like they were in the same room. Real-time multiplayer on mobile demands sub-second sync, graceful disconnection handling, and cheat prevention, all while keeping infrastructure costs near zero.
Before settling on Firestore, I evaluated several real-time database options including Supabase Realtime, Socket.io with a custom backend, and Firebase Realtime Database. Supabase offered PostgreSQL familiarity but lacked the seamless offline support critical for mobile gaming. Socket.io gave full control but meant managing WebSocket infrastructure — a non-starter for a solo developer. Firebase Realtime Database was a contender, but Firestore's document model mapped more naturally to game sessions with nested turn data and player states.
Firestore's real-time listeners turned out to be the perfect fit. Each game session lives as a single document with nested turn data. Both clients subscribe to the same document, and
...
Tags: Flutter, Firebase, Real-time, Game Dev
See Also:
→ The Five-Word Quiz That Fills an Empty Deck on Day One→ AI Agents Are Replacing the Traditional Software Development Lifecycle→ Building a Multi-Tenant Marketplace from Scratch→ PostgreSQL vs Firestore: A Practical Decision Framework→ How GenAI Reduced Our Operational Overhead by 90%Browse all articles →Key Facts
- • Category: Dev
- • Reading time: 16 min read
- • Technology: Flutter
- • Technology: Firebase
- • Technology: Real-time