1. The Problem
Build a temperature converter that takes a value and a unit (Celsius, Fahrenheit, or Kelvin) and converts it to all three scales instantly. Bonus: reject temperatures below absolute zero (−273.15 °C).
Why this project? Pure functions with clear maths, error handling, and floating-point rounding — all in one small program.
2. How to Think About This
Route everything through Celsius as the "hub" unit — convert input to Celsius first, then from Celsius to the other two. This means you only need 4 conversion formulas instead of 6:
- C → F: multiply by 9/5 then add 32
- F → C: subtract 32 then multiply by 5/9
- C → K: add 273.15
- K → C: subtract 273.15
3. The Build
Core logic (converter.mjs)
// Four conversion formulas — Celsius as the hub
export const C_to_F = c => (c * 9/5) + 32;
export const F_to_C = f => (f - 32) * 5/9;
export const C_to_K = c => c + 273.15;
export const K_to_C = k => k - 273.15;
// Convert from any unit to all three, rounding to 4 decimal places
export function convertAll(value, fromUnit) {
// Step 1: convert to Celsius
let celsius;
switch (fromUnit) {
case "C": celsius = value; break;
case "F": celsius = F_to_C(value); break;
case "K": celsius = K_to_C(value); break;
default: throw new Error(`Unknown unit: "${fromUnit}". Use C, F, or K.`);
}
// Step 2: validate — nothing below absolute zero
if (celsius < -273.15) {
throw new RangeError(`${value}°${fromUnit} is below absolute zero (−273.15 °C).`);
}
// Step 3: convert to all three and return
return {
C: +celsius.toFixed(4), // + converts string back to number
F: +C_to_F(celsius).toFixed(4),
K: +C_to_K(celsius).toFixed(4),
};
}Command-line interface (convert.mjs)
import { convertAll } from "./converter.mjs";
// Read arguments: node convert.mjs 100 C
const [,, rawValue, unit] = process.argv;
if (!rawValue || !unit) {
console.error("Usage: node convert.mjs <value> <unit>");
console.error("Example: node convert.mjs 100 C");
process.exit(1);
}
const value = Number(rawValue);
if (isNaN(value)) { console.error("Value must be a number."); process.exit(1); }
try {
const result = convertAll(value, unit.toUpperCase());
console.log(`${value}°${unit.toUpperCase()} =`);
console.log(` ${result.C} °C`);
console.log(` ${result.F} °F`);
console.log(` ${result.K} K`);
} catch (e) {
console.error("Error:", e.message);
process.exit(1);
}4. Test & Prove
import { convertAll, C_to_F, F_to_C } from "./converter.mjs";
let pass = 0, fail = 0;
function test(desc, fn) {
try { fn(); console.log(" ✓", desc); pass++; }
catch(e) { console.log(" ✗", desc, "—", e.message); fail++; }
}
const approx = (a, b) => Math.abs(a - b) < 0.001;
test("water boils: 100°C = 212°F", () => { const r = convertAll(100,"C"); if(!approx(r.F,212)) throw new Error(r.F); });
test("water freezes: 0°C = 32°F", () => { const r = convertAll(0,"C"); if(!approx(r.F,32)) throw new Error(r.F); });
test("absolute zero: 0 K = −273.15°C", () => { const r = convertAll(0,"K"); if(!approx(r.C,-273.15)) throw new Error(r.C); });
test("body temp: 98.6°F ≈ 37°C", () => { const r = convertAll(98.6,"F"); if(!approx(r.C,37)) throw new Error(r.C); });
test("below absolute zero throws", () => { try { convertAll(-300,"C"); throw new Error("should throw"); } catch(e) { if(!(e instanceof RangeError)) throw e; } });
test("unknown unit throws", () => { try { convertAll(100,"X"); throw new Error("should throw"); } catch(e) { if(!(e instanceof Error)) throw e; } });
console.log(`
${pass} passed, ${fail} failed`);
5. The Interface
Input: two command-line arguments — value (number) and unit (C/F/K). Case-insensitive.
Output: the value in all three scales, to 4 decimal places.
$ node convert.mjs 100 c 100°C = 100 °C 212 °F 373.15 K $ node convert.mjs -300 c Error: −300°C is below absolute zero (−273.15 °C).
6. Run It
node convert.mjs 37 C # body temperature
node convert.mjs 212 F # water boiling (Fahrenheit in)
node convert.mjs 0 K # absolute zero
node converter.test.mjs # run tests🎯 Try this next
- Add a browser UI — text input + dropdown for unit; display all three results live as the user types using the
inputevent - Add Rankine and Réaumur scales
- Validate to a sensible maximum (e.g. 15 million K is the core of the sun)
- Accept piped stdin:
echo "100 C" | node convert.mjs
What you practised
Pure functions with a single responsibility, routing through a hub unit to minimise conversion formulas,
process.argv for command-line arguments, custom error types (RangeError vs Error),
and toFixed() + unary + for clean rounding.
Reference: Numbers & Math · Error Handling · Functions