Tier 1 — Beginner

Unit Converter

Convert between length, weight, temperature, and speed — a lookup table of conversion factors, no if/else chains.

🐍 Python 🟨 JavaScript

1. The Problem

Build a unit converter that handles length (km/miles/feet/inches), weight (kg/lbs/g/oz), temperature (C/F/K), and speed (km/h, mph, m/s). Convert any unit to all others in its category.

Why this project? A nested object as a lookup table replaces hundreds of if/else cases. Temperature requires custom functions rather than simple multipliers — a good lesson in when tables don't fit.

2. How to Think About This

For linear units: store a conversion factor to a base unit (e.g. all lengths convert via metres). To convert A→B: convert A to base, then base to B. This means you only need N factors instead of N² pairs. Temperature is non-linear so it gets its own explicit functions.

3. The Build

convert.mjs
// Linear unit lookup — factor = "how many base units is 1 of this?"
export const UNITS = {
  length: { base: "m",
    km: 1000, m: 1, cm: 0.01, mm: 0.001,
    mi: 1609.344, yd: 0.9144, ft: 0.3048, "in": 0.0254 },
  weight: { base: "kg",
    t: 1000, kg: 1, g: 0.001, mg: 0.000001,
    lb: 0.453592, oz: 0.0283495 },
  speed: { base: "m/s",
    "m/s": 1, "km/h": 1/3.6, mph: 0.44704, knot: 0.514444 },
};

// Temperature: non-linear — explicit conversion through Celsius as hub
export function convertTemp(value, from, to) {
  const toC = { C: v=>v, F: v=>(v-32)*5/9, K: v=>v-273.15 };
  const fromC = { C: v=>v, F: v=>v*9/5+32, K: v=>v+273.15 };
  if (!toC[from] || !fromC[to]) throw new Error(`Unknown temp unit: ${from} or ${to}`);
  return +fromC[to](toC[from](value)).toFixed(4);
}

export function convertLinear(value, from, to) {
  // Find the category this unit belongs to
  const category = Object.values(UNITS).find(cat => from in cat && to in cat);
  if (!category) throw new Error(`Cannot convert ${from} to ${to} — different or unknown categories`);
  const inBase = value * category[from];
  return +(inBase / category[to]).toFixed(6);
}

export function convertAll(value, from) {
  // Try linear first
  for (const [catName, cat] of Object.entries(UNITS)) {
    if (from in cat) {
      const results = {};
      for (const unit of Object.keys(cat)) {
        if (unit === "base") continue;
        results[unit] = convertLinear(value, from, unit);
      }
      return { category: catName, results };
    }
  }
  // Try temperature
  const tempUnits = ["C","F","K"];
  if (tempUnits.includes(from)) {
    const results = {};
    tempUnits.forEach(to => { results[to] = convertTemp(value, from, to); });
    return { category: "temperature", results };
  }
  throw new Error(`Unknown unit: ${from}`);
}

const [,, valueStr, from] = process.argv;
if (!valueStr || !from) {
  console.error("Usage: node convert.mjs <value> <unit>");
  console.error("Examples: node convert.mjs 100 km  |  node convert.mjs 37 C  |  node convert.mjs 1 kg");
  process.exit(1);
}
const value = parseFloat(valueStr);
if (isNaN(value)) { console.error("Value must be a number"); process.exit(1); }
const { category, results } = convertAll(value, from);
console.log(`
${value} ${from}  [${category}]`);
Object.entries(results).forEach(([unit, val]) => {
  if (unit !== from) console.log(`  = ${val} ${unit}`);
});

4. Test & Prove

import { convertLinear, convertTemp, convertAll } from "./convert.mjs";
let p=0,f=0;
function test(d,fn){try{fn();console.log("  ✓",d);p++;}catch(e){console.log("  ✗",d,"—",e.message);f++;}}
const approx = (a,b) => Math.abs(a-b) < 0.001;
test("1 km = 1000 m",      () => { if(!approx(convertLinear(1,"km","m"),1000)) throw new Error(); });
test("1 mile ≈ 1.609 km",  () => { if(!approx(convertLinear(1,"mi","km"),1.609)) throw new Error(convertLinear(1,"mi","km")); });
test("100 C = 212 F",      () => { if(!approx(convertTemp(100,"C","F"),212)) throw new Error(); });
test("0 C = 273.15 K",     () => { if(!approx(convertTemp(0,"C","K"),273.15)) throw new Error(); });
test("37 F = 2.778 C",     () => { if(!approx(convertTemp(37,"F","C"),2.778)) throw new Error(convertTemp(37,"F","C")); });
test("1 kg = 1000 g",      () => { if(!approx(convertLinear(1,"kg","g"),1000)) throw new Error(); });
test("cross-category throws",() => { try{convertLinear(1,"km","kg");throw new Error("no");}catch(e){if(e.message==="no")throw e;} });
test("unknown unit throws", () => { try{convertAll(1,"xyz");throw new Error("no");}catch(e){if(e.message==="no")throw e;} });
console.log(`
${p} passed, ${f} failed`);

5. The Interface

node convert.mjs 100 km
# 100 km  [length]
#   = 100000 m  = 62.137 mi  = 328084 ft ...

node convert.mjs 37 C
# 37 C  [temperature]
#   = 98.6 F  = 310.15 K

node convert.mjs 1 kg
# 1 kg  [weight]
#   = 1000 g  = 2.20462 lb ...

6. Run It

node convert.mjs 100 km
node convert.mjs 37 C
node convert.test.mjs

🎯 Try this next

  1. Add area (m², ft², acres, hectares) and volume (L, ml, gallons, fl oz)
  2. Add a --to flag: node convert.mjs 100 km --to mi
  3. Build a browser version with dropdowns for from/to unit
  4. Add currency conversion by fetching a live exchange rate API

What you practised

Lookup table pattern (N factors instead of N² conversions), hub-unit conversion, separating linear from non-linear conversion logic, Object.entries()/Object.keys() for iterating lookup objects.

Reference: Objects · Numbers & Math · Functions

Try It Yourself