DEV Community

Dor
Dor

Posted on

How race time predictors actually work: Daniels VDOT vs Riegel, with the JavaScript

Every race time predictor on the internet is doing one of two things. It is either fitting a power law to your result (Pete Riegel, 1981) or running you through a physiology model (Jack Daniels and Jimmy Gilbert, 1979). I implemented both in plain JavaScript for a running app I work on, so here is how they actually work, with the code.

Method 1: Riegel's power law

Riegel was a mechanical engineer and a competitive runner. In 1981 he fit a simple relationship to world-record performances: finish time scales with distance raised to a small power above 1.

// T2 = T1 * (D2 / D1) ^ 1.06
function riegel(d1, t1, d2) {
  return t1 * Math.pow(d2 / d1, 1.06);
}

// 20:00 5K -> predicted 10K
riegel(5000, 20 * 60, 10000); // ~2497s = 41:37
Enter fullscreen mode Exit fullscreen mode

The exponent 1.06 captures the fact that you cannot hold a pace forever. Double the distance and time slightly more than doubles. It needs no physiology and almost no input. The weakness is that 1.06 was fit to world records, so it flatters amateur marathon predictions.

Method 2: Daniels and Gilbert's VDOT model

Two years earlier, Daniels and Gilbert went through physiology instead of around it. Two equations. The first is the oxygen cost of running at a velocity v in metres per minute:

function vo2(v) {
  return -4.60 + 0.182258 * v + 0.000104 * v * v;
}
Enter fullscreen mode Exit fullscreen mode

The second is the fraction of VO2max you can sustain for a race lasting t minutes:

function pctMax(t) {
  return 0.8 + 0.1894393 * Math.exp(-0.012778 * t)
            + 0.2989558 * Math.exp(-0.1932605 * t);
}
Enter fullscreen mode Exit fullscreen mode

From a race you derive a fitness score, VDOT, then solve for the time at a new distance where oxygen demand matches what you can sustain:

function vdotFromRace(d, t) {           // d metres, t seconds
  const tMin = t / 60, v = d / tMin;
  return vo2(v) / pctMax(tMin);
}

function timeForDistance(d, vdot) {     // bisection on duration
  let lo = 1, hi = 600;
  for (let i = 0; i < 100; i++) {
    const mid = (lo + hi) / 2;
    const implied = vo2(d / mid) / pctMax(mid);
    if (implied > vdot) lo = mid; else hi = mid;
  }
  return (lo + hi) / 2 * 60;
}
Enter fullscreen mode Exit fullscreen mode

They mostly agree, until the marathon

For trained runners predicting between nearby distances, the two methods land within a percent or two of each other. The marathon is where both break down. Vickers and Vertosick (2016) studied recreational runners and found real marathon times ran about 4 to 5 percent slower than the prediction, with much wider scatter.

The reason is not the math. A 5K tests your aerobic ceiling. A marathon tests fat oxidation, glycogen, fluid replacement, heat tolerance, and pacing discipline, none of which a 5K loads. A predictor tells you what is possible if you do the training. It does not tell you what you will run on an untrained marathon.

Showing both is the honest move

The practical takeaway I landed on: run both methods and show the spread. When they agree, trust it. When they diverge by more than a couple percent, that gap is itself information about how far you are extrapolating.

I put both behind a race time predictor and a set of goal-time pace charts if you want to see the outputs without wiring up the code. References for anyone going deeper: Riegel (1981), American Scientist; Daniels and Gilbert (1979), Oxygen Power; Vickers and Vertosick (2016), BMC Sports Science.

Top comments (0)