Understanding NFL Comeback Chances with Data

I’m a data nerd and a casual NFL fan.  Anyone who watches NFL games knows that broadcasters demonstrate knowledge of the most obscure statistics throughout the game.  But there’s one metric that broadcasters don’t have an incentive to tell you:

When is one team so far ahead that there’s a ~zero likelihood of a comeback?

(For the optimists: Tell me if my team still has a chance!)

I decided to answer my own question by building ComebackCeiling.com.

ComebackCeiling has processed >11,000 historical NFL games to identify the point differential between teams – and at each point in the game determine the largest margin that a team has ever come back from to win (or tie/force overtime).  Additionally, I’ve calculated how that margin changes based on if the team behind is playing at home or away, and/or possession of the ball.  (I also added another line for the largest point differential ever seen at each point in the game.)

I’m launching this now because the NFL season is starts in a matter of days and I want to have this available to me as a fan. 😀 Later this year I hope to spend some time hooking it up to live sources of data and have it tweet when a game has gone past the comeback ceiling.  If you have any suggestions, reach out!

Note: I want to credit Gemini and Claude for helping me develop this.  Wrangling data and all sorts of different edge cases is fun, but my scripting skills alone are much worse compared to what I can accomplish with the help of AI.  (Also, my CSS/JS skills are nonexistent compared to AI!)  I wrote a separate (longer) blog post about how I developed the tech behind ComebackCeiling if you’re interested in those details.

Building Comeback Ceiling – live NFL comeback charts – the gritty details

comebackceiling.com answers one question: for any point in an NFL game — any time remaining, any team, with or without the ball — what’s the largest deficit anyone has ever actually come back from?

Getting to a site that answers that question took longer than I expected, and involved resolving source data that had a variety of gaps. This is the story of how it got built — from getting a 9GB folder of downloaded HTML box scores through to analysis and a live site.

Where it started: downloading 13,000+ games

I knew the first thing I needed to do was get data locally that I could query and wrangle – there was no way I knew enough about various edge cases to rely on querying the data each time I needed it. For years I knew my scripting skills were bad enough that it wasn’t worth it to even try. 😂

Last year on a whim I just asked Google Gemini if it could generate a Python script that would download the HTML files with the stats I needed. I was surprised at how quickly I was able to do so; it took a couple rounds of back-and-forth to get the details right, and it took a while because I wanted to be respectful of the sites where I was getting data. But in the end I had a local folder with >9GB of data and over 13,000 individual files!

The first (dumb) schema

I’m not a professional software engineer, so I knew I needed a super-simple schema. So I built something dumb – a table where each column represented each second in the game and each row was an individual game. My reasoning was that it would be very simple to identify the games where there was a comeback, then for each column (aka second of the game) I could query across the rows and calculate the biggest delta. Yes, it was dumb.

The biggest problem I encountered at this point was taking the downloaded HTML files and reliably turning them into rows in the table. There were a lot of gaps in the data which drove sufficient complexity that I put the project to the side for a while.

Turning to Claude

Earlier this summer I was using Claude on something else and decided to re-start this project. I just had Claude check out the directory and evaluate the current state. I was pleasantly surprised at just how quickly it identified the inefficiencies (dumb schema!) and came up with proposals for a different approach.

The key change was a smarter schema. In an NFL game the margin, possession, and number of timeouts only change at discrete moments — just a few dozen scoring/possession/timeout events, not 3,600 per-second states. So instead of a column per second, the key table captured data on intervals.

Each row says “from this second to that second, this was the state of the game.” A query for “what was the score with 90 seconds left” becomes a single range-containment lookup instead of scanning across a specific column. Because game state only changes (at most) a few dozen times a game instead of every second, the whole 13,400-game corpus compressed to reasonable 528,000 rows.

Parsing 13,000+ files

Once the schema existed, the harder problem was actually getting real data into it. Box scores from 1994 onward had a full play-by-play table with clock time, down and distance, and a text description of every play. Older games mostly don’t — just a running score by quarter, no clock, no plays.

(Interestingly, in the year that I had put this project aside, the site had backfilled play-by-play data for the entire 1978-1993 range. After re-scraping the data, I had an even better foundation for analysis.)

The parser itself has to do real archaeology on each page – there were random games that were missing play-by-play data, or had other strange one-off issues that needed to be accounted for. But in the end, 11,352 of the 13,000+ games had genuine play-by-play; the remaining ~2,000 only had the coarser tier flag and had to be excluded from analysis.

Computing the frontier

With clean interval data in place, the actual “frontier” computation was anticlimactic: for each 30 second window of game clock, scan every game, scan every play, and for each team-perspective ask “was this team down by X points, and did they go on to win or tie?” Keep the largest X per bucket.

The whole corpus — 11,352 games — computes in about 10.5 seconds and produces a few thousand precomputed rows, small enough to query directly rather than querying raw timelines on every request. I got confidence in the approach immediately – on the very first run of this code it validated what Google could tell you – the largest comeback in NFL history was 33 points, when the Minnesota Vikings’ came back over Indianapolis in 2022.

Nailing the details

Getting final scores and win/loss outcomes right turned out to be the easy 90%. Getting the timing of every deficit right — exactly which second of the game a team was down by how much — surfaced a series of much subtler bugs. (Which I only found by poking at individual data points I thought were strange – Claude isn’t foolproof!):

  • An off-by-one in period-length detection, where a normal 15:00 kickoff computed as elapsed_second = 1 instead of 0.
  • Missing and sparse timestamps. A handful of games have zero logged play times at all; others have long gaps between timed plays. The first pass defaulted missing times to the start of the game or forward-filled from the last known play — both of which could put a real deficit several minutes away from when it actually happened.
  • A mislabeled quarter-header bug, found by spot-checking a 1982 game against its raw rows: on some pages, the “2nd Quarter” divider is placed one row after that quarter’s first play instead of before it, so the parser attributed a real second-quarter deficit to the literal opening kickoff. This turned out to affect about 1 in 20 games with usable play-by-play. (!)
  • The big one. Poking at games in the final second found bad data. The cause: the last logged play in a game is often a few seconds before the clock actually hits 0:00 so the final interval of almost every game was silently truncated before the true end of regulation. It affected 84% of the entire corpus. (!)  Claude fixed by explicitly extending the final regulation interval to the true end of the period, with regression tests and a full corpus reload to confirm the fix propagated everywhere.

None of these bugs ever changed a final score or a win/loss outcome which is why Claude’s tests and full pipeline runs didn’t catch them. They only changed when in the game a real deficit got attributed – which I identified because the data I wanted to display was a bit verbose.

From data to chart, iteratively

The visualization went through several real iterations. I started by identifying the comeback frontier across all games, then creating separate charts for when the comeback team was the home or away team. Then another set of charts based on if the comeback team had possession at that given time or not.

While for most of the game, 30 second intervals were sufficient, I wanted a second-by-second breakdown for the final two minutes of the game, so included those charts, too. I also wanted to understand the biggest deficit ever in an NFL game (where the team behind couldn’t come back), and added that into the chart.

Finally, once I was happy with the individual charts, I decided to combine them all with simple toggles for the home/away and possession/no-possession conditions, and including the all-time deficit and two-minute charts. (Keen viewers will see that in certain places there are multiple lines on the frontier. I decided that if the frontier line only represented a single game, I wanted to know the next-highest deficit on the frontier and chart that, too.)

After a few styling tweaks, it was ready to ship.

Choosing a name

While building it, I had been referring to it as Win Frontiers, but I was smart enough to recognize that was a crappy name. I asked Claude for help, and it came up with a variety of names, checked them for domain name availability, and I was happy with Comeback Ceiling. It was great to have a thought partner with better taste.

What’s next

At some point I’m hoping to get a live data feed, identify when a game has gone past the Comeback Ceiling, and tweet out to the fans of the team. (And if that team sets a new comeback record, to tweet that out, too!)

Introducing… Bayes Calculator

I’m pleased to announce the launch of Bayes Calculator – a passion project I’ve wanted to build for years. I created it to help people understand and visualize Bayesian statistics and Bayesian inference calculations. Check it out now at bayescalculator.com.

It’s been so long I don’t even remember when I was first introduced to Bayesian statistics, but I believe it was when I got my masters’ in Decision Sciences at the London School of Economics. While frequentist statistics was what I’d always learned growing up (and which is obviously still valuable), learning about Bayesian approaches filled a gap I hadn’t known existed.

Since I finally shut down Seed-DB last year, I’ve finally had some time to build the projects that have been kicking around in my head – this is the first to launch. There are a lot of features I’d like to develop, but in the spirit of “launch early” (and often?), I’m posting about it now, and I welcome any and all feedback. [Contact page or tweet at me.]

(I’d like to thank the newest crop of AI tools, without which it would have taken me easily 100 times as long in order to get this off the ground!)

Best books I read in 2025


I read over 30 books in 2024; these are my favorites. [2024 post]

#1 – A+++ Strongly Recommend

The Power Broker – Robert Caro

There’s a reason most people don’t even try to read this book – it’s over 1100 pages, and the paperback version weighs over 4 pounds. But MY GOD is it a fantastic read!

Robert Caro began his career as an investigative reporter, and his writing style is really engaging like the best investigative pieces. Yes, he goes into incredible detail in certain parts of the book that feels… unnecessary sometimes, but each chapter is juicy.

I learned so much about political power, organizational power, how cities get built, the history of New York City/State, and about how BIG things get built. It’s all about Robert Moses, who built so much of the physical infrastructure in/around New York City and Long Island (parks, highways, bridges, tunnels) — all from unelected positions of power that he held for 40+ years. (!)

If you’re interested in this book, I’d also recommend the 99 percent invisible podcast which did a Power Broker audio book club.

Non-Fiction

Who is Government? The Untold Story of Public Service – Michael Lewis

I love Michael Lewis’ writing, and really appreciate him diving into government employees, highlighting people that are doing outstanding work. (And often doing work that only the government can do.)

Apple in China – Patrick McGee

This was a really fascinating book. I knew that Apple really relies on manufacturing in China, but the history of how that developed, and the consequences of it were new to me. I’ve heard from friends at Apple that argue with some of the details in the book, but the overall themes are consistent and important.

The Endurance Artist: Lazarus Lake, the Barkley & a Race with No End – Jared Beasley

This book features Laz, the creator of the most insanely difficult running races that have ever existed: the Barkley Marathons and the Backyard Ultramarathon. Laz’s races push every person to the absolute limit of the pain and suffering they’re willing and able to endure.

Check out an amazing documentary of the Barkley Marathons here:

Injustice: How Politics and Fear Vanquished America’s Justice Department – Carol Leonnig

If there’s one branch of government that can counter the worst impulses of the Trump administration, it’s the judicial branch. And if there’s one part of the executive branch that has professional ethics and standards, it’s the Justice Department. This book deals with what happened in the Justice Department during Trump’s first administration, and then Biden’s administration, and then the start of Trump’s second administration. It’s a wild tale, with tales of true heroism but also decisions in hindsight that look very unwise. I learned a lot.

Excellent Advice for Living: Wisdom I Wish I’d Known Earlier – Kevin Kelly

This was originally a blog post, but makes an excellent little book with nuggets of wisdom. I ended up highlighting and consolidating the parts that really spoke to me.

Fiction

Silo series – Hugh Howey

After watching the first two series on Apple TV, I decided to read the book series. (The first two series of the TV match up to book #1 in the series; the next two series match up to book #2 and #3, apparently.) If you like the series, you’ll definitely like the books.

Dungeon Crawler Carl series – Matt Dinniman

This is another fun series – it’s a RPG (Role Playing Game) come to life as a series of novels – aka “LitRPG”. It’s not particularly thought-provoking, but it’s a hell of a fun read.