How Software Breaks
The failures that actually happen: bad input, missing state, timing, integration seams and wrong assumptions. Knowing the shapes is how you learn to guess where a bug is hiding.
The failures that actually happen: bad input, missing state, timing, integration seams and wrong assumptions. Knowing the shapes is how you learn to guess where a bug is hiding.
The sign-up form had been live for four months when a woman called Aoife O'Brien tried to use it. The page went white. No error, no message — just white, for every user whose name contained an apostrophe, since launch.
Nobody had tested it, and not because they were lazy. They had tested the form dozens of times. They had just tested it the way the person who built it used it.
By the end of this lesson you will have the recurring shapes bugs come in, a set of questions to ask about any feature, and the habit that separates people who find interesting bugs from people who confirm the happy path still works.
Given a form that takes a name and an email and creates an account, the natural test is: type a name, type an email, press the button, see the account.
It passes. It was always going to pass — that exact sequence is what the developer ran while writing it, possibly twenty times.
The path the feature was built along.
A real name, a real email, one click. Everything behaves.
You have confirmed what everybody already believed, and produced a "pass" that reads like reassurance.
Every path nobody walked while building it.
An email with no @. A name four thousand characters long.
An account that already exists. The button pressed twice.
The network dropping between the press and the response.
None of these is exotic. Every one of them happens daily.
The rest of this lesson is a vocabulary for that second column. There are five shapes, they cover most of what goes wrong, and once you can name them you stop clicking at random.
The largest single category. A value arrives that the code assumed would not.
The checklist for any input, and it is worth learning by heart:
empty "" nothing typed at all
missing (absent) the field was never sent
whitespace " " looks empty, is not
too long 4000 chars is there a limit? enforced where?
too short "a" minimum length, if any
zero 0 often a valid value treated as missing
negative -1 quantity, price, age
wrong type "abc" text where a number is expected
special chars O'Brien apostrophes, quotes, backslashes
unicode 名前, 🙂 emoji, accents, right-to-left text
leading zeros "007" preserved, or silently a number?
boundary 0, 1, max the edges. The next lesson is all edges.Two of those cause more real incidents than the rest combined.
Empty, zero and missing are three different things, and
careless code treats them as one. A quantity of 0 is
meaningful, and it is not the same as no quantity given. A
search for "" is not the same as no search. In many languages
0 and "" are both falsy, so if not quantity silently
swallows a legitimate zero.
Apostrophes and unicode. O'Brien has broken more systems than any deliberate attack. A name in a script the developer did not think about, or an emoji in a display name, finds encoding bugs at every layer of a stack.
There is a two-minute version of this whole section, and it is worth doing on every text field you meet.
Nothing
Submit the field empty.
One space
Looks empty. Usually is not.
Something very long
Paste 4,000 characters.
An apostrophe
O'Brien. The classic.
An emoji
Encoding, end to end.
It takes two minutes and finds something surprisingly often — which tells you how rarely anybody does it.
Software is rarely a function of its inputs alone. It depends on what already happened, and the code was written imagining one particular history.
the first time no data at all — the empty list, the new account
the second time does it handle already having done this?
after a failure half-complete state from a previous attempt
after a change an email changed, a permission revoked, a plan
downgraded — while the user was mid-flow
at the limits nothing, exactly one, exactly the page size,
one more than the page size, ten thousand
as someone else a user who does not own this, is logged out,
or is an adminThe empty state deserves singling out. Developers build with test data present, so a brand-new user with no projects, no history and no avatar hits screens nobody has ever looked at. Empty states are among the most reliably broken parts of any product.
The already-done case is the other big one. Submit the form twice — two accounts, a clear error, or a silent overwrite? Refund an already-refunded order. Cancel a cancelled subscription. Click a one-time link a second time. Code written for a fresh action often has no idea what to do the second time.
The hardest to find and the most expensive to diagnose, because they do not reproduce reliably.
Order. Two things happen in an order nobody planned. A webhook arrives before the record it refers to exists. A page finishes loading after the user has navigated away.
Concurrency. Two users, or two tabs, act on the same thing at once. Both see one seat left, both book it. Two requests both read a balance of 100, both subtract 80, and the account ends at 20 having spent 160.
Slowness. Something takes longer than expected. The user presses the button again. The connection times out mid-write. A token expires between two steps of a flow.
Repetition. The same message is delivered twice — genuinely common in distributed systems, where "at least once" is the usual guarantee. Is charging a card twice acceptable? Almost never, so somebody had to have thought about it.
A surprising share of failures are not inside any component but in the joins between them, because each side made an assumption the other did not share.
the other side is down what does yours do? Hang, or fail well?
the other side is slow is there a timeout? What happens then?
the response is not what was a null where an object was promised,
agreed a new field, a changed error code
the same fact, two places cached value and real value disagree
different units or formats pence and pounds; ISO dates and
epoch seconds; UTC and local
the file upload wrong type, huge, zero bytes, corruptTime zones and money are the two classics, and they earn it. A date stored as UTC and shown as local time is off by a day for some users at some hours. A price handled as a floating-point number accumulates fractions of a penny. Both are boring, both recur forever, both are worth checking on sight.
The sneakiest shape, because nothing is wrong with the code. It was written against a fact about the world that has since changed.
it worked with 100 records now there are 100,000, and the
page takes 40 seconds
one currency became two the format is hard-coded
one time zone became many "the working day" is not universal
a field that was always set is now optional for new users
a downstream API changed the old shape is still assumedThis is where regression comes from: a change in one place breaks something apparently unrelated, because the second thing depended on something the first thing quietly guaranteed. It is also why tests written for old bugs keep earning their keep — a topic of its own later in this course.
Using the shapes deliberately has a name — error guessing — and it works because bugs cluster rather than spread evenly.
Two of the things that make an area risky can be checked in seconds, and together they tell you where to start.
New, but small. Check the obvious inputs and move on.
New and complicated. Almost every serious bug you find will be in this box.
Old and simple, running in production the whole time. It has been tested by reality.
Old and hairy. Stable, but the next change to it will hurt.
There is a third signal the grid cannot show: bugs attract bugs. An area that produced five defects will produce more, because the difficulty that caused them has not gone anywhere.
So "where should I look?" has real answers. What is new? What is complicated? What broke before? What handles money, permissions or personal data? Those four questions point at the risk faster than any amount of systematic clicking.
Bad — a test report that says nothing about risk:
1. Log in as test@example.com
2. Create a project called "Test"
3. Verify the project appears
Result: passGood — the same feature, aimed at where it might break:
1. Create a project with a name of 500 characters
2. Create a project with an empty name
3. Create a project with a name that already exists
4. Create a project called "O'Brien & Sons"
5. Double-click Create and count the projects
6. Create a project as a user with no permission
Results: 3 and 5 both create duplicates. See BUG-412, BUG-413.The first sequence confirms what everyone already believed. It costs the same to run, produces no information, and ends in a "pass" that reads like reassurance.
# 1. INPUT the code did not expect
empty / missing / whitespace-only
too long / too short / zero / negative
wrong type / apostrophes / unicode and emoji / leading zeros
boundaries: 0, 1, max, max+1
watch: empty vs zero vs missing treated as one thing
# The two-minute sweep, on any text field
nothing -> one space -> 4000 chars -> O'Brien -> an emoji
# 2. STATE that is not what the code assumed
the first time — the empty list, the new account, no avatar
the second time — submit twice, refund a refund, reuse a link
after a failure — half-finished state from a previous attempt
after a change — permission revoked, plan downgraded, mid-flow
at the limits — 0, 1, exactly a page, a page plus one, 10,000
as someone else — not the owner, logged out, an admin
# 3. TIMING
order — a webhook before the record it refers to
concurrency — two users, one last seat; two writes, one lost
slowness — timeouts, expiry mid-flow, the impatient re-click
repetition — the same message delivered twice. Charged twice?
# 4. SEAMS between components
the other side is down, or slow — timeout? and then what?
a null where an object was promised; a changed error code
cache and source of truth disagree
units and formats: pence vs pounds, UTC vs local, ISO vs epoch
uploads: wrong type, zero bytes, enormous, corrupt
# 5. ASSUMPTIONS that used to be true
100 records became 100,000 — now it is slow
one currency, one time zone, one locale became several
a field that was always set is now optional
this is where regressions come from
# WHERE TO LOOK FIRST (bugs cluster)
what is new? recent change is risky
what is complicated? branches and special cases attract defects
what broke before? the same difficulty is still there
what is dangerous? money, permissions, personal dataYou now have a vocabulary for where bugs live. The next lesson zooms out to the levels you can look at them from — unit, integration, system and end-to-end — and what each level can and cannot see, which is what decides where a given check belongs.
Before that, take a feature in software you use every day and write down ten ways it could break, using the five shapes. Then try three of them. The first time a two-minute sweep finds something real, the shapes stop being a list and become a reflex.