Kickmates
Real-Time Social Football Prediction Platform
Football prediction app for private groups. Compete on scorelines across multiple leagues with live leaderboards that update the moment a match ends. No gambling, pure bragging rights.
Business Context
Football fans wanted a way to compete on match predictions with friends and colleagues, no gambling, just bragging rights and leaderboard glory. The product needed to support private groups across multiple leagues, with live scoring that updated the moment a match ended, keeping the rivalry alive throughout the season.
Why I Built It
Every weekday before the week's matches, my friends and I were going back and forth on WhatsApp: predictions, banter, arguments about who called it. But the conversations got buried, nobody could track who was actually right, and the whole thing fell apart after a few games. I built Kickmates to keep that energy alive properly. A real leaderboard, real predictions, no more losing track in a chat thread.
The Problem
The core challenge was orchestrating three time-sensitive systems simultaneously: an external football API with hard rate limits, a background scoring engine that had to fire after real matches finished, and a real-time notification layer that pushed leaderboard changes to users the moment they happened.
A naive polling approach would either exhaust the API quota within hours or score predictions minutes late, killing the live experience the product was built on. On top of that, prediction deadlines had to be enforced server-side to prevent late submissions, and any race condition between scoring and deadline enforcement would corrupt leaderboard integrity permanently.
A naive polling approach would either exhaust the API quota within hours or score predictions minutes late, killing the live experience the product was built on. On top of that, prediction deadlines had to be enforced server-side to prevent late submissions, and any race condition between scoring and deadline enforcement would corrupt leaderboard integrity permanently.
The Architecture Decision
I evaluated three approaches for background processing: Celery with a dedicated worker, a cron job hitting the API endpoint, and APScheduler running in-process with FastAPI.
Celery is the right call at scale, but it adds a Redis-backed broker, a separate worker container, and significant operational overhead for a product that didn't need horizontal scaling yet. Cron jobs are simple but stateless: no rate limit awareness, no graceful failure handling.
I went with APScheduler embedded in FastAPI's lifespan with a tiered job schedule: fixture statuses sync once daily at 11 PM UTC, scoring runs every 50 minutes targeting only
Scoring is intentional: 5 points for exact score, 3 for correct outcome, 0 otherwise. After each batch scores, ranks are recalculated with explicit
Celery is the right call at scale, but it adds a Redis-backed broker, a separate worker container, and significant operational overhead for a product that didn't need horizontal scaling yet. Cron jobs are simple but stateless: no rate limit awareness, no graceful failure handling.
I went with APScheduler embedded in FastAPI's lifespan with a tiered job schedule: fixture statuses sync once daily at 11 PM UTC, scoring runs every 50 minutes targeting only
is_scored=False matches, match reminders fire hourly, and gameweek advancement runs nightly at 1 AM. Each tier has a different cadence to stay well inside API-Football's 90 calls/minute limit, enforced by a sliding-window rate limiter with async locking to prevent concurrent jobs racing to the same quota.Scoring is intentional: 5 points for exact score, 3 for correct outcome, 0 otherwise. After each batch scores, ranks are recalculated with explicit
ORDER BY total_points DESC rather than DB triggers: cleaner, auditable, and replayable. Real-time delivery uses Socket.IO, pushing leaderboard updates directly to connected users the moment scoring completes.The Trade-off
APScheduler is single-instance. If the server restarts mid-job or the product needs horizontal scaling, the scheduler doesn't survive without coordination logic. We accepted this because the current scale doesn't justify Celery's overhead, and missed scoring windows recover on the next 50-minute cycle without data loss.
Outcome
Groups can predict across Premier League and La Liga with leaderboards that update within 50 minutes of a match finishing, without ever exhausting the external API quota. Prediction deadlines are enforced server-side with zero race conditions, and a new group is live and ready for predictions in a single API call.
Tech Stack
TypescriptPythonNuxtFastAPIPostgreSQLAPSchedulerSocket.IOAPI-FootballRedis
Link
