← All posts

Why bugs happen, and why finding them early is cheaper

Where website bugs come from, from unclear requirements to time zones, and why the moment you find a bug decides what it costs to fix.

On this page

Every website has bugs. Not because the people who build it are careless, but because a website is a lot of small decisions made by different people, about content nobody has written yet, for devices nobody on the team owns. The question is not whether bugs happen, but when you find them.

This post looks at where bugs come from, why the moment you find one decides what it costs, and what I do to find them as early as possible.

Why bugs happen

Unclear requirements

Many bugs are not mistakes in the code but in the understanding. “The form should send an email” leaves open who receives it, what happens on failure, and whether the visitor gets a confirmation. The code does exactly what was built, and still not what was meant.

Assumptions about data

Code is written against the content that exists while building it. Real content arrives later: a project without a website, a name with an umlaut, a title three times longer than the design expected, an empty list. Every one of those is a branch the code never took during development.

Edge cases nobody sees locally

Some bugs only exist for other people. A classic: dates. A date without a time is parsed as midnight UTC, so formatting it in the visitor's time zone can move it to the previous day:

typescript
// The post was published on 6 November.
const published = new Date("2026-11-06");

published.toLocaleDateString("en-US");
// Berlin:   "11/6/2026"
// New York: "11/5/2026"  ← a day early

On a developer's machine in Berlin, everything looks right. The fix is one option, once you know the bug exists:

typescript
// Date-only strings are parsed as midnight UTC, so format them in UTC too.
published.toLocaleDateString("en-US", { timeZone: "UTC" });
// "11/6/2026" everywhere

Changes that break something else

A change to the header CSS fixes the mobile menu and breaks the footer. A renamed field in the CMS fixes the editor's workflow and empties a section on the home page. These regressions are especially costly because the part that broke was already tested and nobody looks at it again.

Different environments

Safari on an older iPhone, a slow mobile connection, a browser extension that blocks a script, a screen reader. A site that works perfectly in one browser on a fast laptop has only been tested in one of many environments it will run in.

The earlier you find a bug, the cheaper it is to fix

The same bug costs very different amounts depending on when it surfaces. Roughly, these are the stages:

  1. While writing the code: the editor or a type check flags it. You fix it in seconds, with the whole context still in your head.
  2. In review or testing: someone else finds it. You have to switch back into the task, but nobody outside the project is affected.
  3. Before launch, by the client: it costs a round of feedback, a fix, and some trust.
  4. After launch, by a visitor: now it also costs whatever the visitor didn't do: the enquiry that wasn't sent, the purchase that wasn't made. Often nobody reports it at all.

The fix itself is often the same few lines at every stage. What grows is everything around it: finding the cause in code you wrote weeks ago, involving more people, cleaning up data that was saved wrongly, and the customers you lost while the bug was live.

A bug found while typing costs seconds. The same bug found by a customer can cost the customer.

How I find bugs earlier

Let the editor find them

TypeScript in strict mode catches a whole class of bugs before the code even runs. Here the project's website is optional, and TypeScript refuses to pretend otherwise:

src/lib/projects.ts
interface Project {
  title: string;
  website?: string;
}

function domain(project: Project) {
  return new URL(project.website).hostname;
  // error TS2345: Argument of type 'string | undefined' is not
  // assignable to parameter of type 'string | URL'.
}

Two compiler options do most of the work:

tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true
  }
}

Small tests for tricky logic

Not everything needs a test. Logic with edge cases does: prices, dates, anything that formats or calculates. A tiny function like this one:

src/lib/price.ts
export function formatPrice(amount: number, locale: "en" | "de"): string {
  return new Intl.NumberFormat(locale === "de" ? "de-DE" : "en-IE", {
    style: "currency",
    currency: "EUR",
  }).format(amount);
}

…already has a trap in it. German formatting puts a non-breaking space before the euro sign. A test written with a normal space fails, and that failure is exactly the point: it teaches you how the output really looks before a layout or a comparison depends on it.

src/lib/price.test.ts
import { describe, expect, it } from "vitest";
import { formatPrice } from "./price";

describe("formatPrice", () => {
  it("formats euros for German visitors", () => {
    // Intl puts a non-breaking space (\u00a0) before the €, not a normal one.
    expect(formatPrice(1234.5, "de")).toBe("1.234,50\u00a0€");
  });

  it("formats euros for English visitors", () => {
    expect(formatPrice(0, "en")).toBe("€0.00");
  });
});

Real content and real devices, early

I build with real or realistic content as soon as possible, including the awkward cases: the longest title, the missing image, the German translation. And I open the site on an actual phone well before launch, not on the last day.

A second pair of eyes

Every change goes through a review before it's merged, and automated checks run on every pull request. How that pipeline looks is in my post on what QA means for a website.

The takeaway

Bugs are a normal part of building software. What isn't normal is finding them in production when they could have been caught in the editor. Most of the work is moving the discovery earlier: stricter types, small tests where they matter, real content, real devices, and a review before anything goes live.

If your website has bugs that keep coming back, or you want a launch without surprises, get in touch. I'll tell you where I'd look first.