DEV Community

toolzip
toolzip

Posted on

Building a BMI Calculator — The Math, the Korean Standard, and the Limitations

BMI calculators are a common beginner project. The math is straightforward: weight in kg divided by height in meters squared. But implementing one that's actually useful reveals some interesting design decisions — particularly around which standard to apply, how to handle different input units, and how to communicate the limitations of the metric clearly.

The Core Calculation

function calculateBMI(weightKg, heightCm) {
  const heightM = heightCm / 100;
  return weightKg / (heightM * heightM);
}

// Example
const bmi = calculateBMI(70, 175);
// → 22.857...
Enter fullscreen mode Exit fullscreen mode

The Classification Problem

WHO's BMI classifications are widely used:

const WHO_CLASSIFICATIONS = [
  { max: 18.5, label: 'Underweight', risk: 'Malnutrition risk' },
  { max: 25.0, label: 'Normal weight', risk: 'Low risk' },
  { max: 30.0, label: 'Pre-obesity', risk: 'Enhanced risk' },
  { max: 35.0, label: 'Obesity class I', risk: 'Moderate risk' },
  { max: 40.0, label: 'Obesity class II', risk: 'Severe risk' },
  { max: Infinity, label: 'Obesity class III', risk: 'Very severe risk' },
];
Enter fullscreen mode Exit fullscreen mode

But Korean health authorities (대한비만학회) use different thresholds. Asian populations tend to carry proportionally more body fat at the same BMI compared to European populations, and experience obesity-related health risks at lower BMI values:

const KOREAN_CLASSIFICATIONS = [
  { max: 18.5, label: '저체중', labelEn: 'Underweight' },
  { max: 23.0, label: '정상', labelEn: 'Normal' },
  { max: 25.0, label: '비만 전단계', labelEn: 'Pre-obese' },
  { max: 30.0, label: '1단계 비만', labelEn: 'Obesity Class I' },
  { max: 35.0, label: '2단계 비만', labelEn: 'Obesity Class II' },
  { max: Infinity, label: '3단계 비만', labelEn: 'Obesity Class III' },
];
Enter fullscreen mode Exit fullscreen mode

Notice: the Korean standard marks BMI ≥ 23 as "pre-obese" versus WHO's ≥ 25 for "overweight." This 2-point difference can meaningfully change the classification for a large portion of the population.

function classify(bmi, standard = 'korean') {
  const classifications = standard === 'korean' 
    ? KOREAN_CLASSIFICATIONS 
    : WHO_CLASSIFICATIONS;

  return classifications.find(c => bmi < c.max);
}
Enter fullscreen mode Exit fullscreen mode

Handling Both Imperial and Metric Input

Users from different regions prefer different units. Supporting both requires careful conversion:

function calculateBMIFromImperial(weightLbs, heightFt, heightIn = 0) {
  const totalInches = heightFt * 12 + heightIn;
  const heightM = totalInches * 0.0254;
  const weightKg = weightLbs * 0.453592;
  return weightKg / (heightM * heightM);
}

function calculateBMIFromMetric(weightKg, heightCm) {
  const heightM = heightCm / 100;
  return weightKg / (heightM * heightM);
}
Enter fullscreen mode Exit fullscreen mode

A React component that handles both:

function BMICalculator() {
  const [unit, setUnit] = useState('metric');
  const [inputs, setInputs] = useState({
    weightKg: '', heightCm: '',
    weightLbs: '', heightFt: '', heightIn: '',
  });
  const [result, setResult] = useState(null);

  const calculate = () => {
    let bmi;
    if (unit === 'metric') {
      const w = parseFloat(inputs.weightKg);
      const h = parseFloat(inputs.heightCm);
      if (!w || !h || h <= 0) return;
      bmi = calculateBMIFromMetric(w, h);
    } else {
      const w = parseFloat(inputs.weightLbs);
      const ft = parseFloat(inputs.heightFt) || 0;
      const inch = parseFloat(inputs.heightIn) || 0;
      if (!w || (!ft && !inch)) return;
      bmi = calculateBMIFromImperial(w, ft, inch);
    }

    setResult({
      bmi: Math.round(bmi * 10) / 10,
      classification: classify(bmi, 'korean'),
      normalRange: calculateNormalRange(
        unit === 'metric' ? parseFloat(inputs.heightCm) : null
      ),
    });
  };

  // ... render
}
Enter fullscreen mode Exit fullscreen mode

Calculating the Normal Weight Range

Users often want to know not just their classification but their target range:

function calculateNormalRange(heightCm, standard = 'korean') {
  const heightM = heightCm / 100;

  const [minBMI, maxBMI] = standard === 'korean' ? [18.5, 22.9] : [18.5, 24.9];

  return {
    minKg: Math.round(minBMI * heightM * heightM * 10) / 10,
    maxKg: Math.round(maxBMI * heightM * heightM * 10) / 10,
    minLbs: Math.round(minBMI * heightM * heightM * 2.20462 * 10) / 10,
    maxLbs: Math.round(maxBMI * heightM * heightM * 2.20462 * 10) / 10,
  };
}

// Example for 170cm
const range = calculateNormalRange(170, 'korean');
// → { minKg: 53.5, maxKg: 66.2, minLbs: 117.9, maxLbs: 145.9 }
Enter fullscreen mode Exit fullscreen mode

Communicating BMI's Limitations Clearly

BMI is a population-level screening tool, not an individual health assessment. The limitations are real and worth surfacing in your UI:

Muscle vs. fat: BMI measures weight relative to height, not body composition. A highly muscular person can have a high BMI with low body fat. An older person can have a "normal" BMI while having high body fat and low muscle mass.

Distribution: Where fat is stored matters more than total fat. Abdominal fat is metabolically more active and more associated with cardiovascular risk than subcutaneous fat. BMI doesn't capture distribution.

Population-specific: The Korean/Asian standard exists precisely because BMI's predictive value varies by ethnicity. A BMI that predicts a certain level of metabolic risk for a European population predicts different risk levels for an Asian population.

Age and sex: A 60-year-old and a 25-year-old with the same BMI have very different health profiles. Women generally have higher body fat percentages than men at the same BMI.

function BMIDisclaimer() {
  return (
    <div className="disclaimer">
      <p>
        BMI is a screening tool, not a diagnostic measure. It doesn't 
        account for muscle mass, age, sex, or fat distribution. 
        Consult a healthcare provider for a complete health assessment.
      </p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Validation Edge Cases

function validateBMIInputs(weight, height) {
  const errors = [];

  if (weight <= 0) errors.push('Weight must be positive');
  if (height <= 0) errors.push('Height must be positive');

  // Sanity checks for human plausible values
  if (weight > 500) errors.push('Weight exceeds plausible human range');
  if (height < 50 || height > 300) errors.push('Height is outside plausible range (cm)');

  // Extremely low BMI values
  const bmi = calculateBMIFromMetric(weight, height);
  if (bmi < 10) errors.push('Result is below medically possible BMI');
  if (bmi > 100) errors.push('Result exceeds medically recorded maximum BMI');

  return errors;
}
Enter fullscreen mode Exit fullscreen mode

Try It

ToolZip's BMI calculator applies Korean health standards, supports both metric and imperial input, calculates the normal weight range for your height, and works entirely in the browser.

toolzip.app/tools/bmi-calculator


ToolZip — 48 free browser-based tools. Everything runs client-side.

Top comments (0)