Back-to-Back, and Unbeaten

In 2025, Team 449 won the FIRST Chesapeake District Championship as an alliance pick. In 2026, we won it again — this time as the top-seeded, undefeated alliance captain.

"17-0 at District Champs is absolutely insane," President Oliver W. told the team during the long drive home from 2026 Worlds. "Going undefeated is really a hard feat to accomplish. So you guys should all be very proud of yourselves for that."

Just how unusual is it? And how rare is a perfect run during back-to-back titles?

Definitions

First, we need some definitions.

  • District Championship. The capstone event of one of the two competition models of the FIRST Robotics Competition. (Teams qualify for the Championship by earning enough ranking points at two smaller District Events. Most teams compete under this model, which was piloted in Michigan in 2009, although some areas still use the Regional model.)
  • Won / champion. Any team on the alliance that wins the final playoff match of the Championship tournament. This includes alliance captains, their picks, and any backup robot on the field. (449 won in 2025 as a pick, and in 2026 as captain.)
  • Undefeated (strict). Zero losses and zero ties in a team's matches during a championship tournament, including qualification and playoff matches. (Exception: surrogate qualification matches, in which an unrelated team fills for an alliance that's down a robot.)
  • Repeat champion. A team that has won a District Championship in two or more separate seasons.
  • Back-to-back champion. A team that has won two or more consecutive District Championships.

The source

So our question is: how many teams have won a District Championship tournament, won again the following year, and gone undefeated in at least one of them?

The answer lies in the competition records kept by The Blue Alliance, the nonprofit organization that serves as FRC's unofficial archives. Thanks to TBA's public API, we can query them.

We needed to look at every District Championship from 2009 through 2026. We excluded the 2020 season, which was cancelled mid-stream, and 2021, which was largely remote, with no traditional championships. (Note: FIRST California runs two separate state championships, Northern and Southern; each is counted as its own championship event.)

We wrote a script to query the API; see "Methodology," below.

The counts

The Blue Alliance data reveals that from the 2009 to 2026 seasons, a total of 230 distinct teams won a District Championship. (And 67 of those teams have won more than one title, including in non-successive years.)

Back-to-back championships

37 teams have won District Championships in consecutive years:

Team Consecutive years
67 2011–2012
118 2023–2024
176 2022–2023
195 2015–2018 (4 straight)
225 2015–2016, 2018–2019
384 2017–2018
449 2025–2026
469 2012–2013
587 2022–2023
868 2018–2019
1024 2015–2016
1533 2018–2019
1577 2022–2023
1640 2012–2013
1690 2022–2026 (5 straight)
1731 2023–2024
1747 2024–2025
1771 2024–2026
1833 2025–2026
1983 2014–2015
2056 2017–2018, 2022–2026 (5 straight)
2168 2016–2017
2363 2022–2023
2415 2018–2019
2471 2017–2018
2590 2013–2014, 2017–2018
2607 2025–2026
2910 2022–2023, 2025–2026
3005 2023–2025 (3 straight)
3229 2017–2018
4451 2025–2026
4678 2024–2025
5190 2018–2019
5895 2022–2024 (3 straight)
7457 2024–2026 (3 straight)
7617 2022–2023
9496 2024–2025

Undefeated runs

13 tournaments have been won by undefeated teams. Only 2056 has done it twice. Tournaments with larger fields require more matches to win.

Year Team District Wins
2018 2056 FIRST Canada - Ontario 20
2023 7890 FIRST North Carolina 17
2024 836 FIRST Chesapeake 17
2024 2974 Peachtree 17
2024 2056 FIRST Canada - Ontario 19
2025 1833 Peachtree 17
2025 1771 Peachtree 17
2025 4678 FIRST Canada - Ontario 19
2025 6800 FIRST In Texas 19
2026 4414 FIRST California 17
2026 449 FIRST Chesapeake 17
2026 27 FIRST in Michigan 21
2026 6369 FIRST In Texas 19

Back-to-back champs with one undefeated run

Team Title seasons Undefeated title(s) Consecutive run
2056 2017, 2018, 2022–2026 2018, 2024 2022–2026 (5 straight)
1771 2022, 2024, 2025, 2026 2025 2024–2026
4678 2024, 2025 2025 2024–2025
1833 2025, 2026 2025 2025–2026
449 2025, 2026 2026 2025–2026

Eighteen seasons of district play, 230 different championship-winning teams. Just five have managed a back-to-back title with a perfect run in one of their two titles.

BTW: "undefeated" used to be nearly impossible

Through 2022, District Championship playoffs were single-elimination, and every round was a best-of-three series. To emerge undefeated, a team had to sweep every single series 2–0. Under that format, across 14 seasons and 66 championships, exactly one team ever managed it: Team 2056, at the 2018 FIRST Ontario Provincial Championship.

Starting in 2023, FRC moved to an eight-alliance double-elimination bracket. A dominant alliance can now win every match it plays outright, with no best-of-three series standing in the way. The ceiling dropped, and the results show it: since 2023, there have been 12 undefeated championship runs across 48 championships — roughly one in every four or five.

2056 — the only team to do it the hard way, under the old format — is also the most decorated of the five, with seven district titles to its name.

Methodology

Here's the Python script we used to pull and collate the TBA data. (Generated with Claude by non-technical mentor Brad Peniston.)

# Version 1.4 - 2026-09-06
# Lists every undefeated District Championship run, 2009-2026 (2020/2021 excluded),
# and reports summary stats across ALL District Championships (not just undefeated
# ones): total championships held, distinct winning teams, and teams that have won
# more than once, with the years they won.
#
# Undefeated = zero losses, zero ties across quals + playoffs, for a team on the
# winning alliance of the final round. Michigan/Texas/New England/Ontario-style
# division events (event_type 5) are folded into their parent championship event
# via parent_event_key. "Won" = any team on the winning alliance (captain, picks,
# and backup).
#
# Requires: TBA_API_KEY exported in the environment, requests installed.
# Output: team449_dcmp_undefeated_runs.csv and team449_dcmp_all_champions.csv
# in the current directory, plus a printed summary block covering all
# championships, plus a diagnostic dump of every final-level match for any
# event where the fallback was used.

import os
import sys
import csv
import time
import requests
from collections import defaultdict

API_KEY = os.environ.get("TBA_API_KEY")
if not API_KEY:
    print("Missing TBA_API_KEY environment variable.")
    sys.exit(1)

BASE = "https://www.thebluealliance.com/api/v3"
HEADERS = {"X-TBA-Auth-Key": API_KEY}

YEARS = [y for y in range(2009, 2027) if y not in (2020, 2021)]

DISTRICT_CMP = 2
DISTRICT_CMP_DIVISION = 5

def get(path):
    resp = requests.get(BASE + path, headers=HEADERS)
    resp.raise_for_status()
    return resp.json()

def event_matches(event_key):
    return get(f"/event/{event_key}/matches")

def event_alliances(event_key):
    return get(f"/event/{event_key}/alliances")

def team_record(matches, team_key):
    wins = losses = ties = 0
    for m in matches:
        if m.get("comp_level") not in ("qm", "qf", "sf", "f"):
            continue
        alliances = m["alliances"]
        red = alliances["red"]["team_keys"]
        blue = alliances["blue"]["team_keys"]
        if team_key not in red and team_key not in blue:
            continue
        winning = m.get("winning_alliance")
        if winning == "":
            ties += 1
            continue
        side = "red" if team_key in red else "blue"
        if winning == side:
            wins += 1
        else:
            losses += 1
    return wins, losses, ties

def winning_alliance_teams(alliances, champ_matches):
    """Returns (winning_team_keys, used_fallback)."""
    if alliances:
        for alliance in alliances:
            status = alliance.get("status") or {}
            if status.get("level") == "f" and status.get("status") == "won":
                return alliance.get("picks", []), False

    finals = [m for m in champ_matches if m.get("comp_level") == "f"]
    if not finals:
        return [], True
    finals.sort(key=lambda m: m.get("match_number", 0))
    last = finals[-1]
    winning_side = last.get("winning_alliance")
    if winning_side not in ("red", "blue"):
        return [], True
    return last["alliances"][winning_side]["team_keys"], True

def log_finals_detail(event_key, champ_matches):
    finals = [m for m in champ_matches if m.get("comp_level") == "f"]
    if not finals:
        print(f"    {event_key}: no 'f'-level matches found at all")
        return
    finals.sort(key=lambda m: (m.get("set_number", 0), m.get("match_number", 0)))
    print(f"    {event_key}: {len(finals)} final-level match(es) found")
    for m in finals:
        red = m["alliances"]["red"]["team_keys"]
        blue = m["alliances"]["blue"]["team_keys"]
        winner = m.get("winning_alliance") or "(tie/unset)"
        print(
            f"      key={m.get('key')} set={m.get('set_number')} "
            f"match={m.get('match_number')} red={red} blue={blue} winner={winner}"
        )

def build_clusters(cmp_events):
    clusters = {}
    for e in cmp_events:
        if e["event_type"] == DISTRICT_CMP:
            clusters[e["key"]] = {"champ": e, "divisions": []}
    for e in cmp_events:
        if e["event_type"] == DISTRICT_CMP_DIVISION:
            parent = e.get("parent_event_key")
            if not parent:
                continue
            clusters.setdefault(parent, {"champ": None, "divisions": []})
            clusters[parent]["divisions"].append(e)
    return clusters

def main():
    rows = []
    total_championships = 0
    fallback_count = 0
    champion_years = defaultdict(set)

    for year in YEARS:
        events = get(f"/events/{year}")
        cmp_events = [e for e in events if e.get("event_type") in (DISTRICT_CMP, DISTRICT_CMP_DIVISION)]
        clusters = build_clusters(cmp_events)

        for parent_key, cluster in clusters.items():
            champ = cluster["champ"]
            divisions = cluster["divisions"]
            if not champ:
                print(f"{year}: skipping {parent_key}, no champ event found for this parent key")
                continue

            total_championships += 1

            try:
                all_event_keys = [champ["key"]] + [d["key"] for d in divisions]
                district = champ.get("district") or {}
                district_name = district.get("display_name") or champ.get("name")

                all_matches = []
                for ek in all_event_keys:
                    all_matches.extend(event_matches(ek))
                    time.sleep(0.1)

                alliances = event_alliances(champ["key"])
                champ_matches = [m for m in all_matches if m.get("event_key") == champ["key"]]
                winners, used_fallback = winning_alliance_teams(alliances, champ_matches)

                if used_fallback:
                    fallback_count += 1
                    print(f"{year}: fallback used for {champ['key']} (no clear winner from /alliances)")
                    log_finals_detail(champ["key"], champ_matches)

                if not winners:
                    print(f"{year}: could not determine winner for {champ['key']}")
                    continue

                for team_key in winners:
                    team_number = team_key.replace("frc", "")
                    champion_years[team_number].add(year)

                    wins, losses, ties = team_record(all_matches, team_key)
                    if losses == 0 and ties == 0:
                        rows.append({
                            "year": year,
                            "team": team_number,
                            "district": district_name,
                            "wins": wins,
                            "event_keys": ";".join(all_event_keys),
                        })
            except Exception as exc:
                print(f"{year}: error on {champ['key']}, skipping ({exc})")
                continue

        print(f"{year}: done")

    with open("team449_dcmp_undefeated_runs.csv", "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=["year", "team", "district", "wins", "event_keys"])
        writer.writeheader()
        writer.writerows(rows)

    print(f"Wrote {len(rows)} undefeated runs to team449_dcmp_undefeated_runs.csv")

    with open("team449_dcmp_all_champions.csv", "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=["team", "times_won", "years"])
        writer.writeheader()
        for team in sorted(champion_years, key=lambda t: int(t)):
            years = sorted(champion_years[team])
            writer.writerow({
                "team": team,
                "times_won": len(years),
                "years": ";".join(str(y) for y in years),
            })

    print(f"Wrote {len(champion_years)} teams to team449_dcmp_all_champions.csv")

    repeat_champions = {
        team: sorted(years)
        for team, years in champion_years.items()
        if len(years) > 1
    }

    print()
    print("=== District Championship summary, 2009-2026 (2020/2021 excluded) ===")
    print(f"Total District Championships held: {total_championships}")
    print(f"Distinct teams that have won a District Championship: {len(champion_years)}")
    print(f"Teams that have won more than one District Championship: {len(repeat_champions)}")
    print(f"Championships where the winner needed the match-based fallback: {fallback_count}")
    for team in sorted(repeat_champions, key=lambda t: int(t)):
        years = ", ".join(str(y) for y in repeat_champions[team])
        print(f"  Team {team}: {years}")

if __name__ == "__main__":
    main()