Write the Error for the Person Reading It
If the failure path is a design decision, the error message is the part of that decision the user actually sees. And it’s produced under uniquely bad conditions: written in one line by someone deep in the code, who knows exactly what state produced it and therefore can’t easily imagine not knowing. That’s how you get “Error: invalid input” — perfectly clear to the author, who can see the validation rule three lines up, and nearly useless to the person on the other side, who has no idea which input, which rule, or what a valid one would look like. The message is accurate. It’s just not written for its reader.
The gap is one of context. The author has the surrounding code, the variable values, the reason the check exists. The reader has a screen with a sentence on it, usually while trying to accomplish something else, often with no ability to inspect anything. Every piece of context the message omits is context the reader has to reconstruct by guessing, searching, or asking someone — and each of those is dramatically more expensive than the few extra words that would have made it unnecessary. A message that says which field, what was wrong with it, and what would be acceptable turns a support ticket into a five-second fix. That’s an enormous return on one sentence.
Which suggests the test for an error message: what does the person reading this need to do next, and does the message help them do it? For a user-facing validation error, the answer is usually to correct their input, so the message should name the input and the constraint. For an operator seeing a failure in production, the answer is to diagnose and mitigate, so the message needs identifiers, timing, and the specific operation — not just “connection failed” but which connection, to what, after how long, on whose behalf. For a developer hitting an internal invariant, the answer is to fix a bug, so the message should say what was expected versus what was found. Same event, three different audiences, three different messages — and getting that wrong means writing to a reader who isn’t there.
Two failure modes tend to show up in practice. The first is the message that describes the internal state rather than the user’s situation: a stack trace, a raw exception, a code from three layers down. It’s honest but untranslated — it tells the reader what the machine noticed rather than what happened to them. The second is the message so sanitized it says nothing: “something went wrong, please try again.” That one avoids leaking internals but also avoids being useful, and repeated exposure teaches people the software has no idea what’s happening inside itself. Between those poles is the message that names what failed in terms of the reader’s world, and says what they can do about it.
The reason this is worth real attention rather than a passing polish is that error messages are the interface at the exact moment trust is most fragile. Everything is going well until it isn’t, and the message is your only chance to be helpful in a moment the user did not choose. A good one preserves the sense that the software is competent and on their side; a bad one converts a small failure into a much larger impression of unreliability. Getting it right mostly means resisting the pull of your own context — remembering that the person reading this knows nothing you know, and writing the sentence they need rather than the one that’s easy from where you’re standing.