Implementing Matchmaking for Mobile Games
Matchmaking looks simple: put player in queue, find second player, create match. In practice, it's one of most non-trivial server tasks in mobile games. Players with different skill levels shouldn't meet, wait time shouldn't exceed 30-60 seconds, server must choose nearest region for minimum latency—all atomically, without race conditions.
Rating Systems: ELO and MMR
Simplest matchmaking by ELO: each player has rating, server finds opponent with rating ±N points. Problem—on narrow audience, no such player exists, player waits forever.
Solution—expand-and-wait: initial search range narrow (±50 ELO), after 15 sec expands to ±150, after 30 sec to ±300, after 60 sec offers bot match or nearest available player. Each expansion—requery queue.
More complex—Glicko-2: accounts for rating uncertainty (RD, rating deviation). New player has high RD—rating unstable, risky to match. As games increase, RD decreases. More accurate than ELO but more complex.
Queue Architecture
Matchmaking queue—not simple FIFO. Redis implementation:
ZADD matchmaking_queue {elo_score} {player_id}:{timestamp}:{region}
Sorted Set where score = player rating. Find opponent:
ZRANGEBYSCORE matchmaking_queue (min_elo) (max_elo) LIMIT 0 10
Atomicity critical: two matchmakers can't simultaneously claim same player. Lua script in Redis—only atomic "find and delete":
local candidates = redis.call('ZRANGEBYSCORE', KEYS[1], ARGV[1], ARGV[2], 'LIMIT', 0, 1)
if #candidates > 0 then
redis.call('ZREM', KEYS[1], candidates[1])
return candidates[1]
end
return nil
Without this, at horizontal scaling, same player enters two matches simultaneously.
Regional Matchmaking and Latency-Based
For real-time, latency is critical. Client on search start pings several server regions (us-east, eu-west, ap-southeast) and sends measured RTT with queue request. Matchmaker searches for players with overlapping preferred regions.
Unity Gaming Services Matchmaker supports QoS servers for latency measurement out-of-box. Nakama via custom player properties. Custom: client pings UDP-echo servers in each region, sorts by RTT, sends top-3 regions.
Skill Beyond Rating
For some genres, ELO insufficient. Shooters with K/D ratio, strategies with win rate by faction, battle royale with placement history—multidimensional MMR. Each dimension independent, matchmaking searches for "closeness" in multidimensional space.
Simple implementation: weighted distance. K/D weight 0.4, win rate 0.4, overall rating 0.2. Player A: [1.2, 55%, 1500 ELO]. Player B: [1.1, 58%, 1480 ELO]. Distance—weighted norm of difference vector. Below threshold—acceptable match.
Party Matchmaking
Group of 3 players searching for 4th (4v4). Group—one queue unit with averaged rating + penalty for spread within group. Large spread within group—matchmaker finds weaker opponents to compensate.
Creating match on all sides found—atomic transaction: remove all from queue, create room, notify clients via WebSocket or push. If room creation fails—return to queue.
Client States
Client on matchmaking enters states:
IDLE → SEARCHING → FOUND → JOINING → IN_MATCH
Each state—separate UI. SEARCHING shows animation and timer. FOUND—brief "Opponent found" screen (2-3 sec, can't cancel). JOINING—connecting to game server. Cancel available only from SEARCHING.
On client, matchmaking state—StateFlow (Kotlin) or @Published (Swift), updated via WebSocket events.
Timeline
Basic rating-based matchmaking with expand-and-wait for 2 players: 1-2 weeks. Regional matchmaking, multidimensional MMR, party matches: 1-2 months. Cost calculated individually after genre and audience analysis.







