<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: cloudyer</title>
    <description>The latest articles on DEV Community by cloudyer (@cloudyergz).</description>
    <link>https://dev.to/cloudyergz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4227589%2F57d3c5f9-8abc-4298-8c68-08912726d5ec.png</url>
      <title>DEV Community: cloudyer</title>
      <link>https://dev.to/cloudyergz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cloudyergz"/>
    <language>en</language>
    <item>
      <title>What Building a Tournament Bracket Engine Taught Me About Edge Cases</title>
      <dc:creator>cloudyer</dc:creator>
      <pubDate>Thu, 05 Nov 2026 04:33:07 +0000</pubDate>
      <link>https://dev.to/cloudyergz/what-building-a-tournament-bracket-engine-taught-me-about-edge-cases-2e9c</link>
      <guid>https://dev.to/cloudyergz/what-building-a-tournament-bracket-engine-taught-me-about-edge-cases-2e9c</guid>
      <description>&lt;p&gt;When I started building &lt;a href="https://rollare.com/" rel="noopener noreferrer"&gt;Rollare&lt;/a&gt;, 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 &lt;em&gt;around&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;Here are the lessons that cost us the most iterations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Single elimination: seeding is not sorting
&lt;/h2&gt;

&lt;p&gt;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 &lt;em&gt;semifinal&lt;/em&gt;, 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.&lt;/p&gt;

&lt;p&gt;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 — &lt;strong&gt;byes must go to the highest seeds&lt;/strong&gt;, never randomly. We ship a dedicated bye calculator because "how many byes and where" is genuinely the most-asked question organizers have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round robin: the circle method works until it doesn't
&lt;/h2&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Odd team counts&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double round robin&lt;/strong&gt; (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.&lt;/li&gt;
&lt;li&gt;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 &lt;em&gt;group&lt;/em&gt; level, not the tournament level.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Swiss: pairing is a constraint problem wearing a smile
&lt;/h2&gt;

&lt;p&gt;Swiss looks friendlier than elimination and is secretly the hardest. The rules everyone expects:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pair similar records (score buckets).&lt;/li&gt;
&lt;li&gt;Never rematch — players remember, and they will email you.&lt;/li&gt;
&lt;li&gt;Nobody gets two byes.&lt;/li&gt;
&lt;li&gt;Odd team out comes from the bottom bucket, not the top.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Double elimination: the bracket reset
&lt;/h2&gt;

&lt;p&gt;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 &lt;strong&gt;bracket reset&lt;/strong&gt;. 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tiebreakers: the cycle nobody warns you about
&lt;/h2&gt;

&lt;p&gt;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) &lt;em&gt;before&lt;/em&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The export nobody asked for until they all asked
&lt;/h2&gt;

&lt;p&gt;Organizers don't want a PDF of a database table; they want a &lt;strong&gt;poster&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What real events changed
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://rollare.com/" rel="noopener noreferrer"&gt;Rollare&lt;/a&gt;, and the &lt;a href="https://rollare.com/templates" rel="noopener noreferrer"&gt;tournament templates&lt;/a&gt; are a decent shortcut if you're running a standard club event.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>engineering</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
