Turf games average 45.5 points to grass's 43.3 across 7,276 games - but almost the whole 2.2-point gap is the dome, not the surface. Compared within the same environment the gap collapses to under a point. A textbook confound, worked through with the real data.
By C. B. Zakarian · Published July 3, 2026
There is a tidy story that fans and broadcasters like to tell about playing surfaces: artificial turf is a fast, true, predictable track, so offenses fly on it and the points pile up, while natural grass is slower and grabs cleats. The raw numbers seem to back it up. I pulled the complete nflverse game log bundled with this site — 7,276 played games from 1999 through 2025 — and split every game by surface. Turf games average 45.54 total points; grass games average 43.32. A 2.2-point gap looks like a real surface effect.
It mostly isn't. Almost the entire gap is a confound: turf fields are disproportionately installed indoors, and indoor games are higher-scoring for reasons that have nothing to do with the surface. Once you compare grass and turf within the same environment — outdoor-to-outdoor, indoor-to-indoor — the 2.2-point gap collapses to under a point. The surface is a bystander that happened to be standing next to the real cause.
Here is average total points for grass vs turf, first pooled across all games, then within outdoor games only, then within indoor (dome or closed-roof) games only — straight from data_layer/games.csv.
Read the three pairs left to right. Pooled, turf leads by 2.22 points. Restricted to outdoor games, turf leads by just 0.94 (44.04 vs 43.10). Restricted to indoor games, turf leads by 0.80 (47.06 vs 46.26). The moment you stop letting domes hide inside the "turf" column, most of the effect evaporates. What's actually driving the raw gap is the vertical distance between the outdoor pairs (~43–44) and the indoor pairs (~46–47): indoors is worth roughly three points, and turf just happens to sit indoors far more often than grass does.
A confounder is a variable that is tied to both the thing you're measuring and the outcome, creating a correlation that isn't causation. Roof type is the classic case here. Dome and retractable-roof stadiums almost all lay artificial turf, because natural grass struggles without direct sunlight and airflow. So "played on turf" is quietly also "more likely played indoors," and indoor games score more — no wind, perfect footing, climate control — exactly the effect documented in the weather and scoring analysis, where domes top every environment at 46.9 points. Attribute the dome's scoring boost to the turf and you've credited the floor for what the ceiling did.
The fix is the one every observational analysis reaches for: hold the confounder fixed and look again. Comparing turf to grass within outdoor games removes the roof from the equation, and comparing within indoor games removes it from the other direction. Both controlled comparisons agree, and both say the surface itself is worth well under a point — small enough that season-to-season noise and which teams play where could account for it entirely.
You can split the raw 2.22-point advantage into its two pieces. Outdoors, turf beats grass by 0.94 points. Indoors, turf beats grass by 0.80. If the surface were the whole story, the pooled gap would sit near those numbers — around 0.8 to 0.9. Instead it's 2.22, so roughly 1.3 to 1.4 of the 2.2 points come from composition: the turf sample is loaded with high-scoring indoor games (about half of all turf games are indoors, versus a tiny fraction of grass games), which drags the turf average up without any surface effect at all. The genuine surface signal is the ~0.9 point that survives inside a fixed environment — and even that could be teams, not track.
Load data_layer/games.csv, keep rows with a real total, map the surface column to grass vs turf (anything containing "grass" is grass; the rest is artificial), and flag indoor games where roof is dome or closed. Take the mean of total overall, then again within outdoor and within indoor games. The chart and full console breakdown are produced by explainer_src/make_surface_chart.py, which reads the bundled nflverse log directly and stamps a “Data: nflverse” footer. No network, nothing hand-entered.
Want the code behind these metrics? Work through the 45-chapter NFL analytics tutorial.
Browse tutorials Free tools