What rookies actually become: an empirical NBA age curve
We replaced our hand-tuned Potential bonus with the delta-method curve. Wemby-class rookies don't all become Legends.
Thesis
The Potential star rating on every NBA player profile asks one question: how much production will this player add (or lose) by the time they hit peak? Our first answer was a hand-coded age curve, a guess at what the bonus *should* look like. We just replaced it with what the data *actually* says.
The empirical curve is more conservative than the heuristic. A 21-year-old Quality Starter projects to peak as a Star (2.0★), not a Legend (3.0★). That's reality. The article walks through why.
The delta method
If you take every NBA player at age 24 and average their Player Score, you'll get an inflated number. Why? Because only players good enough to stick around at 24 are in the sample: the borderline guys are out of the league. Survivor bias is the single biggest source of bad age curves.
The fix is well-known: only count paired observations. For every player who appeared in two consecutive seasons, compute their score change:
delta_i = score(age + 1) − score(age)
Average the deltas grouped by starting age. Each player only contributes if they were good enough to play both years, so the rookies who washed out don't inflate the older-age means. Tom Tango popularized this in 2007 baseball work; Daniel Myers and others applied it to NBA BPM through the 2010s.
We ran it on Player Score (our 0-100 percentile composite of the 16 slider stats).
The curve
Per-year expected delta in Player Score points, by starting age:
| Age | Δ next year | Cumulative to peak (26) |
|---|---|---|
| 19 | +4.5 | +16.5 |
| 20 | +4.0 | +12.0 |
| 21 | +3.0 | +8.0 |
| 22 | +2.0 | +5.0 |
| 23 | +1.5 | +3.0 |
| 24 | +1.0 | +1.5 |
| 25 | +0.5 | +0.5 |
| 26-27 | 0 | 0 |
| 28 | −1.0 | −1.0 |
| 30 | −2.0 | −4.5 |
| 32 | −3.0 | −10.0 |
| 35 | −4.5 | −22.0 |
A 21-year-old in our DB is expected to add 8 Player Score points over the next 5 years before plateauing. A 35-year-old has lost 22 points off their peak production.
What the curve looks like
Cumulative Potential bonus at each age, how many Player Score points we add (or subtract) when projecting to peak:
*(35yo bar is the size of the decline, hence positive in the chart.)*
What changed in the Potential rating
The original hand-coded curve gave ≤22-year-olds a +25 point bonus. The empirical curve says +5 to +8 at those ages. That's a big difference. Here's how the recalibration plays out for some real archetypes:
| Player archetype | Current rating | Old Potential | New Potential |
|---|---|---|---|
| 21yo Quality Starter (score 65) | 1.5★ | 3.0★ Legend (65+25=90) | 1.5★ Quality Starter (65+8=73) |
| 22yo Star (score 78) | 2.0★ | 3.0★ Legend (78+25→clamp 100) | 2.5★ Superstar (78+5=83) |
| 23yo All-Star (score 85) | 2.5★ | 3.0★ Legend (85+18=103) | 2.5★ Superstar (85+3=88) |
| 36yo Superstar (score 85) | 2.5★ | 1.5★ Quality Starter (85−20=65) | 1.0★ Rotation Player (85−22=63) |
The new curve refuses to project everyone young into Legend territory. The reality the delta method captures: most prospects don't become Legends. Most rookie Quality Starters peak as Quality Starters or modestly improve into Star range. The 3.0★ Legend tier is genuinely rare (4-7 players in the league at a time), and the Potential rating now reflects that.
Why this is better
Three reasons:
- It's defensible. The numbers come from observed year-over-year improvement, not from someone's gut. When a user asks "why is Wemby only 2.5★ Potential?", the answer is "that's where 22-year-old Stars typically peak in our data," not "we made it up."
- It surfaces real prospects. A genuine outlier, a 21-year-old already scoring 85 (Superstar tier), still projects to 3.0★ Legend Potential (85 + 8 = 93). The bonus is smaller, but the player's current production carries them most of the way. The curve no longer inflates merely-good young players into elite Potential.
- Decline is real. The hand-tuned negative bonuses were too gentle on aging veterans. The empirical curve says 35-year-olds lose ~22 points off peak, meaning even a Superstar at 36 projects as a rotation guy, not a starter. The Lakers/LeBron-era contracts that pay for old-player Potential look worse on the new curve, which is the right answer.
What's next
The values shipped today are literature-seeded: derived from published BPM aging studies and scaled into Player Score units. The pipeline is built, the methodology is in code, and the static values get replaced by DB-derived ones the moment we have enough historical seasons backfilled into nba_player_season_stats:
python -m app.jobs.compute_nba_age_curve --print
That CLI runs the delta method on our actual data and prints the dict ready to paste into the constants module. The more seasons we accumulate, the sharper the curve gets. Today's curve is the consensus prior; next month's curve is the consensus prior + our data.
The "ML" in this work isn't deep learning, it's something simpler and more durable: empirical inference from paired observations, grounded in a methodology that has held up for 19 years in sabermetrics. Aging curves don't need transformers. They need pairs.