On September 23, 1999, NASA's Mars Climate Orbiter — a 327-million-dollar spacecraft — approached Mars after a 10-month journey and silently disappeared. It had flown too close to the planet and likely burned up in the atmosphere or bounced off into space. The post-mortem found the cause: one engineering team had been sending thruster force data in pound-force seconds. The software receiving that data assumed newton-seconds. Nobody caught it. Nobody had defined the unit contract between the two systems. A spacecraft was lost because of a type annotation that nobody wrote.
That story gets cited in software engineering talks fairly often, and for good reason — it's a vivid illustration of something that trips up almost every developer who works with measurement data at least once. Units aren't just numbers. They carry meaning that isn't represented anywhere in the data itself, and the gap between two systems that both call "one unit of force" by different names is exactly where bugs hide until they become disasters.
Why two systems exist at all
The short version: one was designed from first principles, and one evolved from what people were already using.
The metric system — more formally the International System of Units (SI) — was developed in France during the Revolution, with the explicit goal of replacing the chaotic patchwork of regional measurements that made trade and science difficult. A meter was defined as one ten-millionth of the distance from the equator to the North Pole. A gram was the mass of one cubic centimeter of water at 4°C. Everything builds on powers of ten, and prefixes — kilo, milli, centi — mean the same thing across every unit type. It's a system designed by engineers for engineers.
The imperial system is older and messier. A foot was roughly the length of a human foot. An inch was three barleycorns laid end to end (this was an actual legal definition in medieval England). A mile is 5,280 feet because that's 8 furlongs, and a furlong is the distance a team of oxen could plow without resting. None of these relationships are round numbers, and none of them derive from anything physically principled. They just got standardized from what people were already measuring.
The "three countries" thing is slightly misleading
You'll often hear that only three countries don't use the metric system: the US, Liberia, and Myanmar. This is technically true for official government adoption, but in practice it's more complicated.
The United Kingdom officially metricated but imperial units remain legally required on road signs (miles, yards), pints are still the unit for draught beer, and people routinely give their weight in stone (14 pounds to a stone, because of course). Aviation globally uses feet for altitude and knots for speed — both imperial-adjacent — regardless of what country the aircraft is in. Nautical miles exist as their own standard, not belonging cleanly to either system. Medicine in the US uses metric for drug dosages while measuring patient weight in pounds and height in feet during the same appointment.
The practical upshot is that even fully metric countries will encounter imperial units in specific domains, and code that assumes "this is a metric country's API, therefore everything is metric" will eventually be wrong.
The NASA story in technical detail
It's worth understanding what actually went wrong with the Mars Climate Orbiter, because it wasn't carelessness — it was a coordination failure of the type that software systems reproduce constantly.
Lockheed Martin's software for the spacecraft's small thrusters output angular momentum values in imperial units: pound-force seconds per second. NASA's navigation software expected the same values in metric: newton-seconds per second. One pound-force second is approximately 4.45 newton-seconds, so every thruster firing was being interpreted as about 4.45 times smaller than it actually was. Over the course of many small trajectory corrections across 10 months of flight, these errors accumulated. By the time the spacecraft reached Mars, it was on the wrong trajectory.
The fix would have been trivial: define the interface contract explicitly. "This function accepts thrust in newton-seconds. If you have pound-force seconds, convert before calling." Somewhere in the system boundary between the two teams, nobody wrote that contract, nobody enforced it at runtime, and nobody caught it in testing because the numbers looked plausible — just slightly off in ways that only become catastrophic at the destination.
How this shows up in software
The most common pattern in day-to-day development: a value comes in from an external source with an ambiguous or implicit unit, and the consuming code assumes the wrong one.
// What does this mean? Miles? Kilometers? Nautical miles?
const distance = response.data.distance;
// What about this? Pounds? Kilograms?
const weight = user.profile.weight;
// And this one — Fahrenheit? Celsius?
const temp = sensor.reading.temperature;
None of these field names tell you the unit. You have to read the documentation, and documentation is sometimes wrong, sometimes missing, and sometimes describes behavior from a previous API version.
A pattern that helps: encode the unit in either the variable name or the type.
// Explicit in naming
const distanceKm: number = response.data.distance_km;
const weightKg: number = user.profile.weight_kg;
// Or use a branded type so the compiler enforces unit correctness
type Kilometers = number & { readonly _brand: 'km' };
type Miles = number & { readonly _brand: 'miles' };
function toKilometers(miles: Miles): Kilometers {
return (miles * 1.60934) as Kilometers;
}
// Now this won't compile:
const dist: Miles = 100 as Miles;
const km: Kilometers = dist; // Type error — good!
The branded type approach is a TypeScript pattern that uses the type system to prevent unit mismatches at compile time. It feels like extra ceremony when you write it, and it feels essential after the first time it catches a bug.
Domains where mixing is especially hazardous
Construction and CAD. AutoCAD, Fusion 360, and similar tools let you set the drawing unit. If someone exports a model assuming millimeters and you import it assuming inches, you get a part that's 25.4x the intended size. In a 3D print, that's a wasted print. In aerospace manufacturing, that's potentially a structural failure.
Fitness and health APIs. Apple HealthKit, Google Fit, and similar APIs have their own canonical units and conversion utilities, but third-party integrations regularly get this wrong. Distance is sometimes in meters, sometimes in kilometers, and occasionally in miles depending on the user's locale setting. If you're building anything in this space, always explicitly request the unit from the API, never assume it from the user's country.
Cooking and recipe apps. This sounds minor, but the number of recipe apps that have silently broken when a user switched their device region settings is non-trivial. A cup is a US cup (240ml) by default in American recipes but 250ml in Australia and Canada. A tablespoon is 15ml in the US but 20ml in Australia. These differences are small enough that most dishes survive them, but if you're building a recipe calculator and your unit conversions use US definitions applied to Australian recipes, the output will be subtly wrong.
Pharmaceutical dosing. Medical software operates almost entirely in metric — milligrams, milliliters, micrograms — but patient weight is often entered in pounds in US healthcare contexts, and dosing calculations frequently use weight. The conversion has to happen somewhere. If it happens twice (someone converts the weight before storing it, and then the dosing formula converts it again) or not at all, the result is a dosing error. This class of bug has real-world consequences, which is why medical software has strict validation requirements that most CRUD apps don't bother with.
The general principle for handling units in code
Store values in a single canonical unit internally. Convert at the edges — at input parsing and at output rendering — and nowhere else. Document the canonical unit in your schema, database comments, and API documentation. If possible, encode it in type names or variable names so that the unit is visible at every usage site, not just where the value was created.
# Bad: unit is implicit
class ProductDimensions:
length: float
width: float
height: float
weight: float
# Better: canonical unit is documented and clear
class ProductDimensions:
"""All dimensions in centimeters, weight in kilograms."""
length_cm: float
width_cm: float
height_cm: float
weight_kg: float
# Conversion happens once, at the boundary
def from_imperial(length_in, width_in, height_in, weight_lb) -> ProductDimensions:
return ProductDimensions(
length_cm=length_in * 2.54,
width_cm=width_in * 2.54,
height_cm=height_in * 2.54,
weight_kg=weight_lb * 0.453592,
)
The discipline here isn't complicated, but it requires being deliberate. Every time a measurement crosses a system boundary — an API call, a file import, a form submission — that's the moment to assert what unit you're receiving and convert to your canonical form. Do it there, document it there, and test it there. Not scattered through the calculation logic where the context is easy to lose.
Need to convert between metric and imperial units without thinking through the math? The Unit Converter covers length, weight, temperature, volume, and more — runs entirely in your browser.