You ran 6 miles at 8:30/mi pace. The route had a 400-foot climb. What's that equivalent to on flat ground — 8:00? 8:15? 8:45?
Grade-Adjusted Pace (GAP) answers that. It converts the actual pace at every grade into the flat-ground pace that would cost the same metabolic energy. Strava paywalls it inside Premium. Garmin Connect doesn't show it at all on most devices. The formula is from a 2002 paper.
Here's how it works.
The physics
Running uphill costs more energy per kilometer than running flat. Running downhill costs less — but only up to a point. Steep enough downhills are more costly than flat, because eccentric loading on the quads burns fuel and damages muscle.
Alberto Minetti measured this directly in 2002 (paper) by putting runners on tilting treadmills and measuring oxygen uptake. He fit a 5th-degree polynomial to the resulting energy cost vs. grade curve.
His formula gives C(g), the energy cost of running per kilogram per meter at grade g (where g is grade as a decimal — 0.05 = 5% uphill):
C(g) = 155.4·g⁵ − 30.4·g⁴ − 43.3·g³ + 46.3·g² + 19.5·g + 3.6 At g = 0 (flat), C ≈ 3.6 J/kg/m. That's the baseline.
At g = 0.10 (10% uphill), C ≈ 7.4 J/kg/m. Running uphill costs roughly twice the energy per meter of horizontal distance.
At g = −0.10 (10% downhill), C ≈ 2.6 J/kg/m — easier than flat.
At g = −0.20 (20% downhill), C ≈ 4.0 J/kg/m — harder than flat. The eccentric-loading penalty kicks in.
Computing GAP from a stream
For each GPS sample (or pair of samples) you have:
- Distance traveled (meters).
- Elevation change (meters).
- Grade
g = elevation / distance. - Energy cost
C(g)from Minetti. - Equivalent flat distance:
equivalent_m = actual_m × (C(g) / C(0)).
Sum the equivalent flat distances over the whole run. Divide by total time. That's GAP.
function minetti(g: number): number {
const g2 = g * g;
const g3 = g2 * g;
const g4 = g3 * g;
const g5 = g4 * g;
return 155.4 * g5 - 30.4 * g4 - 43.3 * g3 + 46.3 * g2 + 19.5 * g + 3.6;
}
const C0 = minetti(0); // ≈ 3.6
export function gapFromStream(samples: { distance_m: number; elev_m: number; t_s: number }[]) {
let equivalentDistance = 0;
let elapsed = 0;
for (let i = 1; i < samples.length; i++) {
const dx = samples[i].distance_m - samples[i - 1].distance_m;
const dh = samples[i].elev_m - samples[i - 1].elev_m;
const dt = samples[i].t_s - samples[i - 1].t_s;
if (dx <= 0 || dt <= 0) continue;
const grade = Math.max(-0.45, Math.min(0.45, dh / dx));
equivalentDistance += dx * (minetti(grade) / C0);
elapsed += dt;
}
const equivalentSpeed = equivalentDistance / elapsed; // m/s
return secondsPerKm(equivalentSpeed);
} 20 lines. Source.
Why your watch might disagree
GAP implementations vary a lot. Common reasons your numbers don't match across apps:
- Different curves. Some apps use Minetti. Strava uses a proprietary tweak. Some watches use a piecewise-linear approximation. The numbers diverge most on steep grades (>8%).
- Grade smoothing window. Raw GPS grade is noisy — a 3-meter elevation jitter over a 5-meter horizontal step looks like a 60% grade. Apps smooth grade over windows ranging from 5m to 30s. Wider smoothing → calmer GAP, but loses fine detail.
- Grade clamping. Most implementations clamp grade to ±30% or ±45% to prevent GPS noise from blowing up the polynomial. Different clamps → different GAPs.
- Elevation source. Barometric altitude (Garmin watches, Apple Watch Ultra) is good. GPS-derived altitude (most phones, older watches) is terrible. SRTM/DEM-corrected (Strava's "elevation correction") is better than GPS but coarse. pacelore uses barometric where available, SRTM otherwise.
- Distance source. GPS distance vs. wheel-circumference vs. accelerometer all give slightly different numbers, especially in tunnels, on switchbacks, or under tree cover.
A 5–15s/mi disagreement on the same run between two apps is normal. A 30s+ gap means one of them is broken.
When GAP is and isn't useful
Useful for:
- Comparing efforts across hilly and flat routes.
- Pacing yourself in races with elevation profiles.
- Evaluating whether a long run was actually "easy" or whether the climbs pushed it into tempo territory.
Not useful for:
- Trail running with technical terrain. The energy cost of jumping over roots and lateral cuts isn't on Minetti's curve. Trail GAP undersells trail efforts substantially.
- Treadmill runs. Most watches log 0% grade regardless.
- Track workouts. The flat-ground assumption already holds; GAP just adds noise.
- Sub-1-mile efforts. Smoothing eats too much of the data.
What GAP doesn't capture
GAP measures metabolic cost of locomotion. It doesn't measure:
- Eccentric muscle damage on long descents (which exceeds metabolic cost on technical or steep downhills).
- Cardiovascular drift in heat or at altitude.
- Heart rate–pace decoupling (a separate metric we compute alongside).
- Stride mechanics — efficiency varies athlete to athlete.
A faster GAP doesn't always mean a faster runner. It means a more economical one over the conditions of that run.
Why it's behind a paywall
GAP was published in a public physiology journal in 2002. The formula is in the paper. The implementation is 20 lines.
Strava paywalls it because it's the kind of feature serious runners specifically want — the same audience most likely to upgrade. It's not a cost issue. It's a price-discrimination strategy.
pacelore computes GAP on every run, shows it next to actual pace, and lets you graph it over the course of the run. Source is open. Use it.
Try it
Live demo — a hilly run with both pace and GAP graphed.
Source — fork, audit, run.
Further reading
- Minetti et al. "Energy cost of walking and running at extreme uphill and downhill slopes."
- Vernillo et al. "Biomechanics and Physiology of Uphill and Downhill Running."
- What is TSS, really? — how GAP feeds into rTSS for runners.