When I started building Rollare, a free bracket maker for local sports clubs, I thought the hard part would be drawing a nice bracket on screen. It wasn't. The hard part turned out to be everything that happens around the bracket: seeding order, bye distribution, rematch avoidance, and the dozen edge cases that only show up when a real organizer with 44 teams and a deadline hits your software on a Saturday morning.
Here are the lessons that cost us the most iterations.
Single elimination: seeding is not sorting
The first naive version sorted teams by seed and paired them 1v2, 3v4, and so on. Any bracket person will tell you that's wrong — it produces a final between seed 1 and seed 2 in the semifinal, not the final. Standard tournament seeding pairs 1 vs the last seed in the first round, which is why a 8-bracket looks like 1v8, 4v5, 3v6, 2v7: the structure exists so that top seeds can only meet as late as possible.
Getting this right for arbitrary sizes means working in powers of two: for a 12-team bracket you pad to 16, mark the missing slots as byes, and — this is the part people get wrong — byes must go to the highest seeds, never randomly. We ship a dedicated bye calculator because "how many byes and where" is genuinely the most-asked question organizers have.
Round robin: the circle method works until it doesn't
Scheduling a round robin is a solved problem — the circle method rotates all teams but one around a fixed pivot. But the edge cases bite:
- Odd team counts need a ghost team, and every ghost match quietly becomes a bye. If you forget to filter ghosts from standings, a "Ghost" row wins the league.
- Double round robin (home and away) is not "run the algorithm twice" — you have to reverse home/away in the second half, or every team plays two consecutive home games and venues complain.
- Duplicate fixtures are the classic bug: if your round generation isn't deterministic per group, a 6-team group can end up with the same matchup twice in one half. We hit this with a real 44-team pickleball event split into 8 groups — deduplication had to happen at the group level, not the tournament level.
Swiss: pairing is a constraint problem wearing a smile
Swiss looks friendlier than elimination and is secretly the hardest. The rules everyone expects:
- Pair similar records (score buckets).
- Never rematch — players remember, and they will email you.
- Nobody gets two byes.
- Odd team out comes from the bottom bucket, not the top.
Rule 2 combined with rule 1 creates a graph matching problem. In small fields you can backtrack; in a 64-team esports qualifier you need a deterministic strategy or you'll time out on a phone. Our approach: sort within buckets, walk the bucket pairing the top remaining with the bottom remaining who isn't a rematch, and only then escalate to crossing buckets. Deterministic beats optimal — organizers care that the result is explainable.
Double elimination: the bracket reset
The grand finals of a double elimination bracket is weird by design: if the winners-bracket finalist loses — their first loss — the tournament isn't over, because both teams now have one loss. You run a bracket reset. If you model your tournament as a linear pipeline instead of a state machine, this "rematch everyone thought was the final" will break your results page, your standings, and your notifications all at once. Match status has to live on the match, not on the tournament.
Tiebreakers: the cycle nobody warns you about
Head-to-head first, then score differential, seems obvious. Then three teams finish in a cycle: A beat B, B beat C, C beat A. Head-to-head is undefined — you need a documented fallback order (differential, then points scored, then a coin-flip procedure) before the tournament starts. We ended up making tiebreak priority configurable per tournament, because a chess club and a volleyball league will argue forever about which metric comes first.
The export nobody asked for until they all asked
Organizers don't want a PDF of a database table; they want a poster they can print and stick on a clubhouse wall or drop into a WhatsApp group. We generate bracket posters as SVG and rasterize them server-side with resvg. Running a rasterizer on an edge runtime is its own adventure — the wasm runtime can't load anything at require-time, so fonts and binaries have to be static bindings baked into the build. That one constraint reshaped half of our build pipeline.
What real events changed
The biggest lesson had nothing to do with algorithms. Real organizers register teams on phones, in a rush, often all within an hour of a deadline (our current record: 19 signups in 32 minutes for a 64-team PES tournament shared in a WhatsApp group). Every flow that assumed "a person calmly fills this in at a desk" lost. If you're building anything event-shaped: registration, the bracket, and live score updates are one product, not three.
If you want to see these decisions in the wild — eight formats from single elimination to swiss, registration, scheduling, and printable results — everything is free at Rollare, and the tournament templates are a decent shortcut if you're running a standard club event.
Top comments (1)
The "deterministic beats optimal" rule for Swiss pairing is a massive lesson for anyone building scheduling tools. Organizers need to be able to explain the results to angry teams, not just trust a perfect algorithm. Also, modeling the bracket as a state machine instead of a linear pipeline for the double-elimination reset is a brilliant architectural call. Great breakdown!