DEV Community

Efficienco
Efficienco

Posted on

What Building 77 Browser-Based Calculators Taught Me About Input Validation

Building one calculator is straightforward. Building dozens of calculators with different units, assumptions, ranges, and failure modes is where input handling becomes the real product.

While building 77 free browser-based calculators for Efficienco, I found that the formula was rarely the part that caused the most trouble. The difficult part was deciding what every input actually meant and what should happen when someone entered something unexpected.

This article covers the validation patterns that became useful across the project. It does not cover the calculation formulas themselves. The focus is everything that must happen before and after a formula runs.

1. Empty, zero, and invalid are different states

A common mistake is treating every falsy value as missing:

if (!value) {
  showError('Enter a value');
}
Enter fullscreen mode Exit fullscreen mode

This rejects 0, even when zero is a valid input. It also fails to explain whether the user left the field empty or entered something the calculator could not parse.

I started handling those states separately:

function readFiniteNumber(input) {
  const raw = input.value.trim();

  if (raw === '') {
    return { ok: false, reason: 'empty' };
  }

  const value = Number(raw);

  if (!Number.isFinite(value)) {
    return { ok: false, reason: 'invalid' };
  }

  return { ok: true, value };
}
Enter fullscreen mode Exit fullscreen mode

The calculator can then give a useful response instead of one generic error for everything.

2. Validate the domain, not only the data type

12 is a valid number, but it may still be invalid for a particular field.

A percentage may need to stay between 0 and 100. A wall length cannot be negative. A pipe diameter of zero makes no physical sense. A financial time period may need to be a whole number.

Type validation answers:

Is this a number?

Domain validation answers:

Is this number meaningful here?

I found it useful to keep those checks explicit:

function requireRange(value, { min, max, label }) {
  if (value < min || value > max) {
    return `${label} must be between ${min} and ${max}.`;
  }

  return null;
}
Enter fullscreen mode Exit fullscreen mode

The ranges should reflect the field's meaning rather than being copied across every calculator.

3. Normalize units at the boundary

When a calculator supports feet, metres, inches, millimetres, gallons, litres, kilograms, and pounds, it becomes tempting to perform conversions throughout the formula.

That quickly becomes difficult to audit.

The cleaner pattern was:

  1. Read the user's value.
  2. Convert it into one internal base unit.
  3. Run the calculation using only base units.
  4. Convert the final result into the requested display unit.

This creates a clear boundary between input formatting and the underlying calculation. It also makes unit-switching tests much easier: equivalent measurements should produce equivalent results.

4. Never round intermediate values

Rounding early can create surprisingly large differences when several stages are involved.

For example, a material estimate might calculate an area, apply a coverage factor, add waste, and finally round up to purchasable units. Rounding after every stage compounds the error.

The safer rule is:

Keep full precision internally and round only for display or purchasing requirements.

Display rounding and operational rounding are also different. Showing 12.47 to a user is a formatting choice. Rounding a purchase requirement up to 13 packages is part of the result's meaning.

5. Assumptions belong beside the input

Many calculators cannot produce a useful result without assumptions. Material waste, labour rates, compaction, occupancy, efficiency, and safety factors are examples.

Hiding those assumptions inside the JavaScript makes the result look more precise than it really is.

I found three things helped:

  • Provide a sensible default.
  • Let the user change it when practical.
  • Explain what the default represents beside the field.

The goal is not to show every implementation detail. It is to ensure users understand which inputs materially affect the answer.

6. Error messages should explain the repair

“Invalid input” describes the software's problem, not the user's next action.

More useful messages are specific:

  • “Wall length must be greater than zero.”
  • “Waste percentage must be between 0% and 100%.”
  • “Enter a valid pipe diameter.”
  • “The minimum value cannot exceed the maximum value.”

The best validation messages tell users exactly what to change without making them understand the implementation.

7. Test relationships, not just individual values

Some inputs are valid on their own but invalid in combination.

Examples include:

  • A minimum value greater than the maximum.
  • A start date later than the end date.
  • A wall opening larger than the wall.
  • A down payment greater than the purchase price.
  • A result unit that does not match the selected measurement system.

These relational checks are easy to miss when every field is tested independently.

The biggest lesson

Users judge a calculator by whether the answer feels dependable, not by how elegant its formula looks in the source code.

That dependability comes from correctly handling messy input, explaining assumptions, preserving precision, and producing useful errors.

One practical example is the Concrete Block Calculator, where dimensions, openings, spacing assumptions, waste, and purchasing quantities all need clearly separated treatment. The public tool demonstrates the experience, while the implementation remains private.

Top comments (0)