Coder's Bracket 2025

shawn posted a pic said
4

It’s been a long time since I’ve filled out a March Madness bracket. The last time I used code to fill one out back in 2016 we won @mfladd a trip to Disney World (see @shawn, You Sweet Inglorious Bastard!). And then I got a sweet t-shirt out of the deal that his kids picked out for me during their trip as a thank you!

This all started back in 2014 when I created an algorithm based on the statistical winning percentage over the past 29 years of each seed broken down by round. This approach was a disaster (see Code Bracket. Because Science.).

In 2015 I made a small tweak that also takes into account the team’s winning percentage (adjusted for strength of schedule using the Rating Percentage Index). This approach got me into the top 90.2% of all brackets in a code competition (see Coders Bracket 2015).

In 2016 I made a tweak to further adjust based on the deltas of the two team’s 3-point percentage, field goal percentage, and free throw percentage. This was the Disney award winning approach (see Coder’s Bracket 2016).

The code bracket competition shut down in 2017 so I stopped doing them because gathering all the data is quite a chore…

…but now we have AI.


Luckily, the Wayback Machine has a saved copy of my 2016 algorithm: https://web.archive.org/web/20160825233719/https://www.codersbracket.com/code_bracket/56e700edb3c99cd90063074a

The bigger challenge was gathering all the data needed for this year’s March Madness tournament. A few prompts with ChatGPT 4.5 and “deep research” that was released earlier this year (https://openai.com/index/introducing-deep-research/) and I have a JSON file that looked like this:

[
{
"name": "Auburn",
"seed": 1,
"defensive_rebound_percentage": 70,
"free_throw_rate": 33.5,
"three_point_percentage": 30,
"turnover_percentage": 11.5
},
{
"name": "AlabamaState",
"seed": 16,
"defensive_rebound_percentage": 68.9,
"free_throw_rate": 29.9,
"three_point_percentage": 32.9,
"turnover_percentage": 13.2
},
{
"name": "SaintFrancis",
"seed": 16,
"defensive_rebound_percentage": 70,
"free_throw_rate": null,
"three_point_percentage": 33,
"turnover_percentage": null
},
{
"name": "MichiganState",
"seed": 2,
"defensive_rebound_percentage": 75,
"free_throw_rate": 30.599999999999998,
"three_point_percentage": 36,
"turnover_percentage": 15
},
{
"name": "Bryant",
"seed": 15,
"defensive_rebound_percentage": 68,
"free_throw_rate": null,
"three_point_percentage": 34,
"turnover_percentage": 18
},
{
"name": "IowaState",
"seed": 3,
"defensive_rebound_percentage": 73,
"free_throw_rate": null,
"three_point_percentage": 35,
"turnover_percentage": 16
},
{
"name": "Lipscomb",
"seed": 14,
"defensive_rebound_percentage": 67,
"free_throw_rate": 38.4,
"three_point_percentage": 37,
"turnover_percentage": 17
},
{
"name": "TexasAM",
"seed": 4,
"defensive_rebound_percentage": 75,
"free_throw_rate": 36.4,
"three_point_percentage": 34,
"turnover_percentage": 15
},
{
"name": "Yale",
"seed": 13,
"defensive_rebound_percentage": 70,
"free_throw_rate": 29.9,
"three_point_percentage": 35,
"turnover_percentage": 16
},
{
"name": "Michigan",
"seed": 5,
"defensive_rebound_percentage": 74,
"free_throw_rate": 31,
"three_point_percentage": 33,
"turnover_percentage": 14
},
{
"name": "UCSD",
"seed": 12,
"defensive_rebound_percentage": 68,
"free_throw_rate": 38.3,
"three_point_percentage": 38,
"turnover_percentage": 17
},
{
"name": "OleMiss",
"seed": 6,
"defensive_rebound_percentage": 72,
"free_throw_rate": 30.599999999999998,
"three_point_percentage": 34,
"turnover_percentage": 18
},
{
"name": "SanDiegoState",
"seed": 11,
"defensive_rebound_percentage": 74,
"free_throw_rate": 28.7,
"three_point_percentage": 34,
"turnover_percentage": 16
},
{
"name": "NorthCarolina",
"seed": 11,
"defensive_rebound_percentage": 75,
"free_throw_rate": 32.6,
"three_point_percentage": 32,
"turnover_percentage": 15
},
{
"name": "Marquette",
"seed": 7,
"defensive_rebound_percentage": 71,
"free_throw_rate": 27.6,
"three_point_percentage": 35,
"turnover_percentage": 16
},
{
"name": "NewMexico",
"seed": 10,
"defensive_rebound_percentage": 72,
"free_throw_rate": 31.2,
"three_point_percentage": 33,
"turnover_percentage": 17
},
{
"name": "Louisville",
"seed": 8,
"defensive_rebound_percentage": 73,
"free_throw_rate": 33,
"three_point_percentage": 34,
"turnover_percentage": 18
},
{
"name": "Creighton",
"seed": 9,
"defensive_rebound_percentage": 70,
"free_throw_rate": 28.299999999999997,
"three_point_percentage": 36,
"turnover_percentage": 14
},
{
"name": "Duke",
"seed": 1,
"defensive_rebound_percentage": 75,
"free_throw_rate": 28.999999999999996,
"three_point_percentage": 37,
"turnover_percentage": 15
},
{
"name": "American",
"seed": 16,
"defensive_rebound_percentage": 69,
"free_throw_rate": 28.000000000000004,
"three_point_percentage": 35,
"turnover_percentage": 17
},
{
"name": "MountStMarys",
"seed": 16,
"defensive_rebound_percentage": 72,
"free_throw_rate": 29.799999999999997,
"three_point_percentage": 33,
"turnover_percentage": 18
},
{
"name": "MississippiState",
"seed": 8,
"defensive_rebound_percentage": 77,
"free_throw_rate": 34.4,
"three_point_percentage": 31,
"turnover_percentage": 19
},
{
"name": "Baylor",
"seed": 9,
"defensive_rebound_percentage": 70,
"free_throw_rate": 33.800000000000004,
"three_point_percentage": 35,
"turnover_percentage": 17
},
{
"name": "Oregon",
"seed": 5,
"defensive_rebound_percentage": 73,
"free_throw_rate": 32,
"three_point_percentage": 34,
"turnover_percentage": 16
},
{
"name": "Liberty",
"seed": 12,
"defensive_rebound_percentage": 70,
"free_throw_rate": 28.000000000000004,
"three_point_percentage": 39.5,
"turnover_percentage": 15
},
{
"name": "Arizona",
"seed": 4,
"defensive_rebound_percentage": 72,
"free_throw_rate": 29.9,
"three_point_percentage": 35,
"turnover_percentage": 17
},
{
"name": "Akron",
"seed": 13,
"defensive_rebound_percentage": 74,
"free_throw_rate": 31.4,
"three_point_percentage": 36,
"turnover_percentage": 15
},
{
"name": "BYU",
"seed": 6,
"defensive_rebound_percentage": 71,
"free_throw_rate": 33.1,
"three_point_percentage": 37,
"turnover_percentage": 18
},
{
"name": "VCU",
"seed": 11,
"defensive_rebound_percentage": 70,
"free_throw_rate": 32,
"three_point_percentage": 34,
"turnover_percentage": 15
},
{
"name": "Wisconsin",
"seed": 3,
"defensive_rebound_percentage": 76,
"free_throw_rate": 28.1,
"three_point_percentage": 35,
"turnover_percentage": 14
},
{
"name": "Montana",
"seed": 14,
"defensive_rebound_percentage": 69,
"free_throw_rate": 29.799999999999997,
"three_point_percentage": 34,
"turnover_percentage": 17
},
{
"name": "SaintMarys",
"seed": 7,
"defensive_rebound_percentage": 75,
"free_throw_rate": 27.800000000000004,
"three_point_percentage": 36,
"turnover_percentage": 15
},
{
"name": "Vanderbilt",
"seed": 10,
"defensive_rebound_percentage": 70,
"free_throw_rate": 28.999999999999996,
"three_point_percentage": 35,
"turnover_percentage": 16
},
{
"name": "Alabama",
"seed": 2,
"defensive_rebound_percentage": 72,
"free_throw_rate": 34.2,
"three_point_percentage": 35,
"turnover_percentage": 18
},
{
"name": "RobertMorris",
"seed": 15,
"defensive_rebound_percentage": 68,
"free_throw_rate": 30,
"three_point_percentage": 37,
"turnover_percentage": 16
},
{
"name": "Houston",
"seed": 1,
"defensive_rebound_percentage": 78,
"free_throw_rate": 34.4,
"three_point_percentage": 39.8,
"turnover_percentage": 14
},
{
"name": "SIUEdwardsville",
"seed": 16,
"defensive_rebound_percentage": 71,
"free_throw_rate": 32.9,
"three_point_percentage": 34,
"turnover_percentage": 17
},
{
"name": "Gonzaga",
"seed": 8,
"defensive_rebound_percentage": 72,
"free_throw_rate": 33,
"three_point_percentage": 37,
"turnover_percentage": 16
},
{
"name": "Georgia",
"seed": 9,
"defensive_rebound_percentage": 70,
"free_throw_rate": 32,
"three_point_percentage": 34,
"turnover_percentage": 18
},
{
"name": "Clemson",
"seed": 5,
"defensive_rebound_percentage": 73,
"free_throw_rate": 30.2,
"three_point_percentage": 36,
"turnover_percentage": 15
},
{
"name": "McNeese",
"seed": 12,
"defensive_rebound_percentage": 70,
"free_throw_rate": 32,
"three_point_percentage": 35,
"turnover_percentage": 17
},
{
"name": "Purdue",
"seed": 4,
"defensive_rebound_percentage": 75,
"free_throw_rate": 29.599999999999998,
"three_point_percentage": 33,
"turnover_percentage": 16
},
{
"name": "HighPoint",
"seed": 13,
"defensive_rebound_percentage": 68,
"free_throw_rate": 31,
"three_point_percentage": 35,
"turnover_percentage": 18
},
{
"name": "Illinois",
"seed": 6,
"defensive_rebound_percentage": 72,
"free_throw_rate": 32.6,
"three_point_percentage": 33,
"turnover_percentage": 16
},
{
"name": "Texas",
"seed": 11,
"defensive_rebound_percentage": 71,
"free_throw_rate": 33.5,
"three_point_percentage": 34,
"turnover_percentage": 17
},
{
"name": "Xavier",
"seed": 11,
"defensive_rebound_percentage": 69,
"free_throw_rate": 31.4,
"three_point_percentage": 38.8,
"turnover_percentage": 15
},
{
"name": "UCLA",
"seed": 7,
"defensive_rebound_percentage": 74,
"free_throw_rate": 32.9,
"three_point_percentage": 34,
"turnover_percentage": 14
},
{
"name": "UtahState",
"seed": 10,
"defensive_rebound_percentage": 70,
"free_throw_rate": 32,
"three_point_percentage": 38,
"turnover_percentage": 18
},
{
"name": "Tennessee",
"seed": 2,
"defensive_rebound_percentage": 77,
"free_throw_rate": 28.999999999999996,
"three_point_percentage": 35,
"turnover_percentage": 16
},
{
"name": "Wofford",
"seed": 15,
"defensive_rebound_percentage": 68,
"free_throw_rate": 30.3,
"three_point_percentage": 36,
"turnover_percentage": 15
},
{
"name": "Florida",
"seed": 1,
"defensive_rebound_percentage": 74,
"free_throw_rate": 32,
"three_point_percentage": 38,
"turnover_percentage": 15
},
{
"name": "NorfolkState",
"seed": 16,
"defensive_rebound_percentage": 67,
"free_throw_rate": 31,
"three_point_percentage": 33,
"turnover_percentage": 17
},
{
"name": "UConn",
"seed": 8,
"defensive_rebound_percentage": 73,
"free_throw_rate": 34,
"three_point_percentage": 35,
"turnover_percentage": 16
},
{
"name": "Oklahoma",
"seed": 9,
"defensive_rebound_percentage": 72,
"free_throw_rate": 32.2,
"three_point_percentage": 34,
"turnover_percentage": 18
},
{
"name": "StJohns",
"seed": 2,
"defensive_rebound_percentage": 79,
"free_throw_rate": 28.9,
"three_point_percentage": 34,
"turnover_percentage": 15
},
{
"name": "Omaha",
"seed": 15,
"defensive_rebound_percentage": 68,
"free_throw_rate": 32,
"three_point_percentage": 35,
"turnover_percentage": 19
},
{
"name": "TexasTech",
"seed": 3,
"defensive_rebound_percentage": 74,
"free_throw_rate": 31.8,
"three_point_percentage": 38,
"turnover_percentage": 14
},
{
"name": "UNCWilmington",
"seed": 14,
"defensive_rebound_percentage": 70,
"free_throw_rate": 31.5,
"three_point_percentage": 33,
"turnover_percentage": 16
},
{
"name": "Maryland",
"seed": 4,
"defensive_rebound_percentage": 75,
"free_throw_rate": 30,
"three_point_percentage": 34,
"turnover_percentage": 15
},
{
"name": "GrandCanyon",
"seed": 13,
"defensive_rebound_percentage": 69,
"free_throw_rate": 32.2,
"three_point_percentage": 36,
"turnover_percentage": 17
},
{
"name": "Memphis",
"seed": 5,
"defensive_rebound_percentage": 70,
"free_throw_rate": 31,
"three_point_percentage": 34,
"turnover_percentage": 17
},
{
"name": "ColoradoState",
"seed": 12,
"defensive_rebound_percentage": 71,
"free_throw_rate": 30,
"three_point_percentage": 36,
"turnover_percentage": 15
},
{
"name": "Missouri",
"seed": 6,
"defensive_rebound_percentage": 68,
"free_throw_rate": 33.1,
"three_point_percentage": 35,
"turnover_percentage": 14
},
{
"name": "Drake",
"seed": 11,
"defensive_rebound_percentage": 73,
"free_throw_rate": 29.799999999999997,
"three_point_percentage": 37,
"turnover_percentage": 15
},
{
"name": "Kansas",
"seed": 7,
"defensive_rebound_percentage": 75,
"free_throw_rate": 30,
"three_point_percentage": 34,
"turnover_percentage": 15
},
{
"name": "Arkansas",
"seed": 10,
"defensive_rebound_percentage": 72,
"free_throw_rate": 33.1,
"three_point_percentage": 31,
"turnover_percentage": 17
},
{
"name": "Kentucky",
"seed": 3,
"defensive_rebound_percentage": 76,
"free_throw_rate": 31.5,
"three_point_percentage": 36.5,
"turnover_percentage": 14
}
]

This didn’t give me exactly everything we had before (we’re missing RPI ratings) but we picked up a few new bits of data (free-throw rate, defensive rebound percentage, etc). Using this data I coded up some quick tweaks to our 2016 algorithm. Here’s the 2025 version:

const teams = require('./2025.json');
const percentages = {
round1: { 1: 100, 2: 94, 3: 85, 4: 78, 5: 68, 6: 66, 7: 60, 8: 48, 9: 52, 10: 40, 11: 34, 12: 35, 13: 22, 14: 15, 15: 6, 16: 0 },
round2: { 1: 87, 2: 69, 3: 61, 4: 56, 5: 52, 6: 51, 7: 27, 8: 18, 9: 8, 10: 46, 11: 38, 12: 49, 13: 24, 14: 12, 15: 14, 16: 0 },
round3: { 1: 79, 2: 72, 3: 50, 4: 35, 5: 21, 6: 33, 7: 37, 8: 70, 9: 40, 10: 33, 11: 33, 12: 5, 13: 0, 14: 0, 15: 0, 16: 0 },
round4: { 1: 59, 2: 46, 3: 47, 4: 72, 5: 75, 6: 23, 7: 0, 8: 57, 9: 50, 10: 0, 11: 60, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0 },
round5: { 1: 57, 2: 48, 3: 64, 4: 23, 5: 50, 6: 67, 7: 0, 8: 50, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0 },
round6: { 1: 67, 2: 33, 3: 44, 4: 33, 5: 0, 6: 50, 7: 0, 8: 50, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0 }
};
function predictWinner(game, team1, team2) {
let lowerSeededTeam = team1;
let higherSeededTeam = team2;
if (team1.seed === team2.seed) {
lowerSeededTeam = (team1.net_ranking < team2.net_ranking) ? team1 : team2;
higherSeededTeam = (lowerSeededTeam === team1) ? team2 : team1;
} else if (team2.seed < team1.seed) {
lowerSeededTeam = team2;
higherSeededTeam = team1;
} else {
lowerSeededTeam = team1;
higherSeededTeam = team2;
}
let percentage = percentages['round' + game.round][lowerSeededTeam.seed];
// 3-point percentage
percentage += (lowerSeededTeam.three_point_percentage - higherSeededTeam.three_point_percentage) * 1.5;
// Free Throw Rate (FTA/FGA)
if (lowerSeededTeam.free_throw_rate && higherSeededTeam.free_throw_rate) {
percentage += (lowerSeededTeam.free_throw_rate - higherSeededTeam.free_throw_rate) * 1.4;
}
// Defensive rebound percentage
percentage += (lowerSeededTeam.defensive_rebound_percentage - higherSeededTeam.defensive_rebound_percentage) * 1.3;
// Turnover rate (negative adjustment since fewer turnovers are better)
percentage += (higherSeededTeam.turnover_percentage - lowerSeededTeam.turnover_percentage) * -1.2;
const random = Math.random() * 100;
if (random <= percentage) {
return lowerSeededTeam; // lower seed advances
} else {
return higherSeededTeam; // higher seed advances
}
}

And here’s the results:

How are you all filling out your brackets this year?