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?
First, we need some definitions.
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 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.)
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 |
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 |
| 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.
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.
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()