Karvonen Formula (Recommended – Uses Resting HR)
Standard Formula (% of Max HR)
Your Training Zones
Zone
Intensity
Heart Rate Range (BPM)
Training Benefit
Optimizing Performance with Heart Rate Zones
Heart rate training is the cornerstone of effective cycling performance. By dividing your effort levels into distinct zones, you can target specific physiological adaptations, from burning fat to increasing your VO2 max. This calculator helps you define those zones precisely using your individual biometrics.
The Difference Between Calculation Methods
There are two primary ways to calculate cycling heart rate zones:
Standard Formula (% of Max HR): This is a simple calculation that takes a percentage of your maximum heart rate. While easy to use, it does not account for your fitness level (represented by your resting heart rate).
Karvonen Formula (Heart Rate Reserve): This method is generally preferred for athletes. It calculates "Heart Rate Reserve" (HRR) by subtracting your resting heart rate from your max heart rate. The training percentages are applied to the HRR and then added back to your resting rate. This creates zones that scale with your fitness improvements.
Understanding the 5 Cycling Zones
Each zone triggers a different metabolic response in your body:
Zone 1: Active Recovery (< 60%)
This is very light spinning. You should be able to hold a conversation easily. Use this zone for warm-ups, cool-downs, and recovery rides to flush out metabolic waste without adding fatigue.
Zone 2: Endurance (60% – 70%)
Often called the "all-day" pace. Training in Zone 2 builds mitochondrial density and improves your body's ability to use fat as fuel. Pro cyclists spend a vast majority of their training time here to build a massive aerobic base.
Zone 3: Tempo (70% – 80%)
This is a challenging but sustainable aerobic pace, often described as "comfortably hard." It improves aerobic efficiency and muscle glycogen storage. However, spending too much time here can lead to fatigue without the specific benefits of high-intensity intervals.
Zone 4: Lactate Threshold (80% – 90%)
This is the intensity you can sustain for about an hour (similar to Time Trial pace). Training here raises your "FTP" (Functional Threshold Power), allowing you to ride faster for longer before lactic acid buildup forces you to slow down.
Zone 5: VO2 Max (90% – 100%)
Maximum effort sustainable for only a few minutes. Intervals in this zone increase the maximum amount of oxygen your body can process. This is crucial for attacks, hill climbs, and sprints.
How to Find Your True Max Heart Rate
The "220 minus age" formula is only a rough estimate. For accurate zones, consider performing a field test. A common protocol involves a thorough warm-up followed by a 20-minute all-out time trial effort, where the highest heart rate recorded in the final sprint represents your functional maximum.
function estimateMHR() {
var ageInput = document.getElementById('cyclistAge');
var maxHRInput = document.getElementById('maxHR');
var age = parseFloat(ageInput.value);
if (!isNaN(age) && age > 0) {
// Standard formula: 220 – Age
var estimatedMax = 220 – age;
maxHRInput.value = estimatedMax;
} else {
alert("Please enter a valid age to estimate Max HR.");
}
}
function calculateZones() {
var age = parseFloat(document.getElementById('cyclistAge').value);
var rhr = parseFloat(document.getElementById('restingHR').value);
var mhr = parseFloat(document.getElementById('maxHR').value);
var method = document.getElementById('calcMethod').value;
var resultDiv = document.getElementById('resultsArea');
var tbody = document.getElementById('zonesTableBody');
var methodDisplay = document.getElementById('methodUsedDisplay');
// Validation
if (isNaN(mhr) || mhr <= 0) {
alert("Please enter a valid Maximum Heart Rate.");
return;
}
if (method === "karvonen") {
if (isNaN(rhr) || rhr = mhr) {
alert("Resting Heart Rate must be lower than Maximum Heart Rate.");
return;
}
}
// Clear previous results
tbody.innerHTML = "";
resultDiv.style.display = "block";
var zones = [];
if (method === "karvonen") {
methodDisplay.innerText = "Calculation Method: Karvonen (Heart Rate Reserve)";
var hrr = mhr – rhr;
// Karvonen Formula: TargetHR = ((MaxHR − RestingHR) × %Intensity) + RestingHR
zones = [
{ name: "Zone 1", desc: "Active Recovery", minPct: 0.50, maxPct: 0.60, ben: "Recovery, Warm up" },
{ name: "Zone 2", desc: "Endurance", minPct: 0.60, maxPct: 0.70, ben: "Fat Burning, Aerobic Base" },
{ name: "Zone 3", desc: "Tempo", minPct: 0.70, maxPct: 0.80, ben: "Aerobic Capacity" },
{ name: "Zone 4", desc: "Threshold", minPct: 0.80, maxPct: 0.90, ben: "Lactate Threshold, FTP" },
{ name: "Zone 5", desc: "VO2 Max", minPct: 0.90, maxPct: 1.00, ben: "Max Effort, Speed" }
];
for (var i = 0; i < zones.length; i++) {
var z = zones[i];
var minBPM = Math.round((hrr * z.minPct) + rhr);
var maxBPM = Math.round((hrr * z.maxPct) + rhr);
addRow(z.name, z.desc, minBPM, maxBPM, z.ben, i + 1);
}
} else {
methodDisplay.innerText = "Calculation Method: Standard (% of Max HR)";
// Standard Formula: MaxHR * %Intensity
zones = [
{ name: "Zone 1", desc: "Active Recovery", minPct: 0.50, maxPct: 0.60, ben: "Recovery, Warm up" },
{ name: "Zone 2", desc: "Endurance", minPct: 0.60, maxPct: 0.70, ben: "Fat Burning, Aerobic Base" },
{ name: "Zone 3", desc: "Tempo", minPct: 0.70, maxPct: 0.80, ben: "Aerobic Capacity" },
{ name: "Zone 4", desc: "Threshold", minPct: 0.80, maxPct: 0.90, ben: "Lactate Threshold, FTP" },
{ name: "Zone 5", desc: "VO2 Max", minPct: 0.90, maxPct: 1.00, ben: "Max Effort, Speed" }
];
for (var i = 0; i < zones.length; i++) {
var z = zones[i];
var minBPM = Math.round(mhr * z.minPct);
var maxBPM = Math.round(mhr * z.maxPct);
addRow(z.name, z.desc, minBPM, maxBPM, z.ben, i + 1);
}
}
}
function addRow(zoneName, intensity, min, max, benefit, zoneNum) {
var tbody = document.getElementById('zonesTableBody');
var tr = document.createElement('tr');
tr.className = 'z' + zoneNum + '-row';
var tdName = document.createElement('td');
tdName.innerHTML = "" + zoneName + "";
var tdInt = document.createElement('td');
tdInt.innerText = intensity;
var tdRange = document.createElement('td');
tdRange.innerHTML = "" + min + " – " + max + " BPM";
var tdBen = document.createElement('td');
tdBen.innerText = benefit;
tr.appendChild(tdName);
tr.appendChild(tdInt);
tr.appendChild(tdRange);
tr.appendChild(tdBen);
tbody.appendChild(tr);
}