← All articles

How we built Table Online — a multiplayer backgammon game in Flutter for web, iOS and Android

Engineering lessons from building a real-time game in Flutter, with the same code running in the browser and natively on mobile. State management, server sync, 60fps performance.

A few months ago we shipped Table Online — a classic multiplayer backgammon game you can play directly in your browser at table-online.ro, or as a native app on iPhone and Android. All of it is built in Flutter, with a single source codebase for all three platforms. This article is an engineering look behind the scenes.

Why Flutter for a multiplayer game

The choice wasn’t obvious. For games, many people reflexively recommend Unity or a custom engine. We picked Flutter for three concrete reasons:

  1. Animations are first-class in Flutter. AnimationController, AnimatedBuilder, Hero, TweenAnimationBuilder — all motion data for backgammon pieces and dice is already solved by the framework. We didn’t have to write a physics engine.
  2. The web lets you adopt without downloading. The biggest friction in mobile game adoption is installation. With Flutter web, the user lands, taps twice, plays a game. If they like it, they install the app later.
  3. One codebase, three platforms — and this isn’t just a marketing line. Game logic, move rules and server communication are shared by all three apps and behave identically everywhere.

Architecture

In short:

  • Flutter frontend — UI, animations, local input. State management with Riverpod.
  • Dart backend on Serverpod — auth, profiles, friends, tournaments, chat, in-app purchases and admin, with a type-safe API shared with the Flutter client.
  • Go realtime service — the game authority for live matches: WebSocket connections, one goroutine per game room, every move validated on the server. The client only proposes.
  • PostgreSQL and Redis — profiles, match history and the leaderboard in PostgreSQL; sessions, the matchmaking queue and live match state in Redis.

Update (October 2026): at launch the whole stack was Dart, and Serverpod also handled the matches. Later we moved live match traffic into a small Go service that runs next to Serverpod, on the same PostgreSQL and Redis. Everything else is still Dart.

The decision that mattered most: the server is source-of-truth. The client proposes a move, the server decides if it’s valid, then broadcasts the result to both players. This eliminates an entire class of cheating and makes debugging predictable.

Performance: holding 60fps in the browser

Flutter web is harder than native mobile on the performance front. A few things made the difference:

  • RepaintBoundary around the board — we isolate board repaints from the rest of the UI. Moving a piece doesn’t repaint the score.
  • HTML renderer for text-heavy pages, CanvasKit for the game page — Flutter web supports two renderers and you can pick per build. For the game page, CanvasKit wins clearly.
  • Lazy loading assets — piece textures, sounds, victory animations only load when needed.

The most useful lesson

If I were starting over, I’d start with the web variant before mobile. It’s harder (Flutter web has fewer tools and more edge cases), but it forces correct architectural decisions early — bundle size, lazy loading, image optimization. The same decisions make the mobile build better later.


Want to discuss a Flutter project? Get in touch — we reply within one business day. And if you want to see the result in action, hop on table-online.ro and play a round.