#.Overview
Polla Mundialista is a bilingual, mobile-first prediction game built around the summer 2026 football tournament. You pick the exact final score of each match, compete with friends in private pools, and watch the standings move as results come in. No money, no betting — just the bragging rights of calling a 2–1 that nobody else saw coming.
I built it end to end as a personal product under a hard external deadline (the tournament won’t wait), in English and Spanish from day one, with an editorial “sports magazine” look — clean type, generous whitespace, one confident accent color. It’s for the kind of people who already run a prediction pool with friends every few years on a shared spreadsheet, and would rather have something that scores itself, settles disputes automatically, and works the same on every phone in the group chat. It shipped mobile-first and now runs on iOS, Android, and the web — the same product, the same rules, wherever you open it.
#.The Problem
Friendly prediction pools have a recurring failure mode: they live in spreadsheets and group chats.
Someone volunteers to be the commissioner. They tally everyone’s guesses by hand against the real results, get a formula wrong somewhere around the knockout rounds, and have to redo it. There’s no real-time leaderboard. No clean way to belong to more than one pool at once. No deadline beyond the honor system. The fun part — calling scores with friends — gets buried under spreadsheet chores and arguments about whose bonus didn’t apply.
I wanted to replace all of that with something automatic, trustworthy, and pleasant: predictions that lock at kickoff, scoring that resolves itself the moment a match ends, leaderboards that are always correct because no human is keeping them.
There was a second problem, too — one that shaped the product as much as the experience design did. The tournament’s branding is fiercely protected intellectual property, so a fan product cannot legally name the event everyone is playing along with. That constraint had to be handled in the copy, in the store listings, in the marketing, and in the code itself. More on that below.
#.How I Wanted It to Feel
Before any screens or schemas, I wrote down what the product should feel like to use. Five lines I would treat as a tiebreaker every time a small product call came up:
- Calm and confident. This is a fan product, not a casino. No bright reds, no live odds, no urgency theater.
- Fast and clear. People will open the app for ten seconds at a time, between meetings or during halftime. The next thing to do should always be obvious.
- Fair by construction. Scoring, deadlines, and standings should not require trust in a person. They should be visibly automatic.
- Bilingual on equal footing. English and Spanish from day one, both first-class, not a translation bolt-on.
- Restrained in branding. A small visual system that scales — one accent, one type family, intentional whitespace — instead of a parade of decorations.
Every design decision in what follows is downstream of those five lines.
#.The Experience
The core loop is short and addictive: predict, join a pool, get scored automatically, see where you stand. Everything else serves that loop.
You start at the landing screen. It carries a kickoff countdown to the opening match and a plain-language note that the app is an independent fan project, not affiliated with any official body — language I treat as canonical and reuse wherever the question might come up.
Once you’re in, the schedule is home base. Matches sit on a vertical timeline with quick filters for today, tomorrow, all days, and unpredicted — that last one is the filter people actually use, because it answers the only question that matters before kickoff: what haven’t I picked yet?
Tapping into a match opens the prediction editor. You set an exact scoreline with plus/minus steppers, see the kickoff time and the round’s multiplier, and watch a live countdown to the moment your pick locks. The scoring breakdown sits right there — 10 points for an exact score, 4 for the correct result — so the stakes are never a mystery.
After saving, the same screen flips into a locked-in view: your pick is preserved, the Edit affordance stays available until the deadline, and the scoring panel remains visible so you remember exactly what you’re playing for.
My Predictions is the personal scoreboard — running total, exact-score and correct-result counts, current streak, and a chronological list of every pick with its status at a glance. It’s the screen you open between matchdays to remember where you stand and what’s still in flight.
The social layer lives in groups. Creating one is intentionally simple: name the pool, drop in a one-line description, choose whether anyone with the invite code can join or whether it’s PIN-protected, and decide whether new members need your approval. Whoever creates the group becomes its admin.
#.The Scoring System
The points model rewards precision while still giving credit for reading a match correctly. An exact final scoreline is worth 10 points. The correct result — right winner, or a draw — but a missed score earns 4 points. A wrong call earns nothing.
To keep later rounds exciting, knockout matches carry multipliers that grow as the stakes do: a Round of 32 match is worth ×1.25, a Round of 16 ×1.5, a quarter-final ×2, a semi-final ×2.5, and the final or third-place match ×3. Knockouts add one more wrinkle — a flat +2 bonus for correctly calling which team advances, layered on top of the (multiplied) base points rather than scaled with them. Knockout scoring uses the 90-minute result, so extra time and penalties don’t muddy what counts as a “correct” prediction. The net effect is that a sharp call late in the tournament can swing a leaderboard in a way an early-round pick never could.
#.Bringing It to the Web
The app shipped mobile-first, but plenty of people wanted to glance at the standings from a laptop without installing anything. So the same product now runs on the web, built in Next.js and drawing from the exact same design system, the same backend functions, and the same English/Spanish copy as the phone apps.
The interesting part wasn’t rebuilding the screens — it was deciding what not to copy. A phone can lean on push notifications and native sharing; a browser tab can’t, and faking them would have felt cheap. So I replaced them with things the web does well. Instead of a push when your matches get scored, the web app keeps a quiet “new results since your last visit” inbox with a badge in the nav. Instead of a deadline notification, a gentle “picks due” banner when kickoff is near. Data is cached in the browser and only polls a match while it’s actually live, so an open tab left on a second monitor isn’t quietly running up the read bill.
What made the whole thing tractable was a bet I’d made months earlier: the design system as the single source of truth. Because color, type, spacing, and the component vocabulary all lived in one platform-agnostic place, the web client started out pixel-aligned with the phone apps instead of drifting into a slightly-different second look. The thing I’d hoped for when I set that up — “any future surface starts aligned for free” — is precisely what happened.
#.Beyond the Core Loop
Two additions went in once the fundamentals were solid, both designed to cost almost nothing to run.
The first is a public stats feed. After each match is scored, the app publishes a small, cached, read-only summary anyone can see: how many people nailed the exact score, the crowd’s most-popular prediction, the average points the match paid out. It reuses numbers the scoring pipeline has already computed, so it’s nearly free to produce — and it turns each result into a shared talking point (“only a handful of us called that 2–1”) instead of a private tally. A “Match stats” link opens it, and because it’s a public page it works even for people who never signed up.
The second is a tournament recap — “Resumen del Torneo” — a Spotify-Wrapped-style, swipeable retrospective of the whole event: total players, predictions made, the biggest pool, and a spotlight on the top scorer with your own points beside it. It reads from a single precomputed document — the same numbers for everyone, so it stays cheap no matter how many people open it — and shipped behind a feature flag, dark and silent, ready to switch on at the right moment without cutting a new release.
#.Behind the Scenes
The product is small in surface area but has strict correctness expectations — points must always be right, deadlines must always hold, and the same pick has to behave consistently across every pool you belong to. A handful of principles shaped how I got there.
A design system as the single source of truth for the visual language. Colors, type, spacing, and radii live in one platform-agnostic package. The app reads from it, and any future surface — a marketing site, a public leaderboard — starts pixel-aligned without copy-pasting hex codes around. The visual language becomes a property of the system, not a habit of whoever is editing this screen today.
Separate the rules from the plumbing. Everything that decides how the product behaves — scoring, deadlines, leaderboard math, invite codes — lives as pure functions with no dependency on the backend, and is covered exhaustively by tests. The code that wires those rules to data is intentionally thin: load, decide, save. Confidence in the scoring stops being a matter of crossing your fingers on match day and becomes something you can prove offline in milliseconds.
Server is the source of truth. Clients never write to the database directly. Every change — a saved prediction, a joined pool, an approved member — goes through a server-side function. That’s what makes deadlines trustworthy: the lock-at-kickoff rule is enforced against the server’s own clock, not the user’s phone, so nobody can sneak a late edit by changing their device time.
React to events, don’t poll. A naive version of this app would constantly ask a sports data feed whether scores had changed. I did the opposite. One scheduled job pulls fixture and result data once a day; when a match transitions to finished, the system reacts and scores every affected prediction automatically. No loops, no wasted reads, no waiting for the next sweep — the points appear because the world changed, not because a timer fired.
Pay for reads once, not per viewer. As real usage arrived, the dominant cost wasn’t compute — it was database reads piling up every time someone opened a leaderboard or an activity feed. So the expensive answers are computed once, the moment a match scores, and written to compact summary documents the read path can serve directly: group ranks are denormalized, per-match activity is pre-rolled, and the live listeners that used to watch the entire fixture list were narrowed to just the viewer’s own picks and memberships. That work cut server-side reads by roughly 90% on the busiest paths, with no change to anything anyone sees. Correctness was never negotiable; this made it affordable at scale, too.
Tests and conventions, not trust. Test-driven development throughout, full coverage on the rules layer, all work on feature branches. The continuous-integration pipeline does more than run tests — it guards conventions, so the kinds of small mistakes that quietly misbehave in production get caught in pull requests instead. Better there than in the standings on match day.
#.Branding as a Product Constraint
Most of the IP constraints on this product aren’t legal footnotes — they’re rules the experience and the codebase have to enforce together.
Because the app can’t name the tournament it’s built around, every user-facing string, every store-listing field, and every piece of marketing copy has to stay clear of protected trademarks. Trusting humans to remember that, release after release, is a losing bet. So I made it a build-time guarantee: a check fails the build if a forbidden term sneaks into the copy. Brand safety becomes a property the system maintains, not a thing a reviewer has to catch by eye.
There’s also a language nuance unique to a bilingual product. The word “polla” means “prediction pool” across much of Latin America — which is why it’s in the app’s name — but in Spain it’s vulgar slang. So the product name stays as it is, while ordinary Spanish copy uses the neutral, universally understood “quiniela” instead. It’s a small thing that matters a lot to how the app reads to a Spanish-speaking audience, and the kind of detail that only surfaces when you take a second language seriously from the start rather than bolting it on later.
The landing screen carries a plain-language non-affiliation note as the canonical statement of what the app is and isn’t. When the same question comes up in store listings or marketing, I reuse that exact language instead of reinventing it.
#.The Stack
Cross-platform mobile in React Native (Expo) and a web client in Next.js, a serverless backend on Firebase, a shared design-tokens package, and shared validation schemas so every client and the server agree on what’s valid. The web app uses TanStack Query for its caching layer; a small, local-only admin panel (Vite + React) drives privileged operations through a single audited endpoint rather than a sprawl of one-off tools. Form handling, internationalization, state, and end-to-end testing all use well-established libraries — boring on purpose, so I could spend the interesting hours on the product, not on the tooling.
#.In Production
Polla Mundialista went live at pollamundialista.app ahead of the opening match — bilingual in English and Spanish, on the App Store and rolling out through an open beta on Google Play, with the web app following shortly after. Since then it has done the thing it was built to do: run itself through a live tournament.
Results score themselves as matches finish, leaderboards stay correct without anyone tending them, and predictions lock on the server’s clock exactly as designed. Real match days also brought the ordinary realities of running against a live data feed — a weather-delayed kickoff that started later than scheduled, load climbing as the knockouts drew bigger crowds — and each got diagnosed and handled without anyone’s standings skipping a beat. The performance work that cut reads by ~90% wasn’t premature optimization; it was the difference between the app shrugging off a busy matchday and me watching a billing graph.
So the honest framing has changed. It’s no longer “built, tested, ready for the matches to begin” — it’s “carried a real tournament, under real load, and behaved.”
#.Reflections
A few things carried this project.
The first was treating the design system as the source of truth — one place for the visual language, so the app stayed coherent and, when the time came to add a web client, that whole surface arrived pixel-aligned instead of as a second look to reconcile. The payoff wasn’t visible on day one; it showed up the day a new platform cost a fraction of what it should have.
The second was keeping the rules separate from the plumbing. Knowing the scoring was correct, deadlines were enforced, and a single prediction counted consistently across every pool stopped being a worry and became something I could prove. That confidence is what lets you move quickly without breaking the things that matter.
The third was treating constraints as design inputs rather than annoyances. The IP rules around branding, the bilingual nuance around a single word, the deadline of a tournament that won’t reschedule itself — none of those went away by complaining about them. Building around them honestly produced a better product than ignoring them would have.
There’s a fourth thing I only appreciated after launch: the gap between shipping and operating. Getting the app into stores was the milestone everyone sees; carrying it through a live tournament — scoring resolving itself, reads staying cheap under load, a delayed kickoff handled without drama — was where the earlier discipline quietly paid its way. The unglamorous decisions made in January are what made July uneventful.
None of these are exotic disciplines. What this project reaffirmed is how much leverage they give you under a real deadline, and through a real tournament — the standards aren’t what slow you down, they’re what let you go fast, and then let you sleep while the matches score themselves.