Skip to content

Hyrum's Law: Why Every Observable Behavior Becomes a Contract

Scribelet Team
13 min read

The change was correct. The function's documentation said it returned results in no particular order, and for years it had happened to return them sorted, purely as a side effect of how it was implemented. When someone rewrote it to be faster, the new version returned the same results in a different order, still perfectly within the documented contract. The tests passed. The code review approved. And within a day a customer's export was broken, because somewhere in their pipeline they had written code that assumed the order they always saw, the order nobody ever promised them. Technically they were wrong to depend on it. The documentation had told them not to. It did not matter even slightly. Their integration was down, the bug was now yours to explain, and the only thing that had changed on your side was something you were explicitly allowed to change.

That gap, between what you promised and what people actually built on, has a name. It is Hyrum's law, and once you have seen it you start seeing it everywhere: the accidental behavior that hardened into a requirement, the "internal detail" that turned out to be load-bearing, the bug you could not fix because too many people had come to rely on it. Keeping track of which behaviors you actually meant to promise is the whole difference between a system you can still change and one you are afraid to touch, and it is exactly the kind of thing worth writing down where it will not get lost. Most explanations give you the one-sentence version and a shrug. What is more useful is knowing exactly which of your behaviors are quietly becoming contracts, how to decide which ones you can still change, and what the teams who fight this at scale actually do about it. Here is Hyrum's law in full, and a practical way to work with it instead of being ambushed by it.

What is Hyrum's law?

Hyrum's law states: with a sufficient number of users of an API, it does not matter what you promise in the contract, all observable behaviors of your system will be depended on by somebody. It was coined by Titus Winters and named after his Google colleague Hyrum Wright, who kept running into the same phenomenon while maintaining code used across Google's enormous shared codebase. It is sometimes called the law of implicit interfaces, and that alternate name is the clearer one: your real interface is not the one you documented, it is the sum of every behavior anyone can observe.

The word doing the work is observable. Your documented contract is a deliberate, narrow promise: this endpoint returns these fields, this function accepts these arguments. But a running system leaks far more than its contract. It returns things in some order. It responds in some amount of time. It formats its error messages a particular way. It happens to return an empty list rather than null, or null rather than an empty list. Every one of those is observable, and Hyrum's law says that with enough people watching, each one will eventually be something a piece of code somewhere relies on, whether or not you ever meant it to.

A small inner box labeled documented contract sits inside a much larger box of observable behaviors like output order, timing, error text, and null handling, with the gap labeled what people actually depend on

This is why the law lands harder than it first reads. It is not a warning about careless users. It is a statement about scale: the larger your user base, the smaller the difference between "a behavior of the current implementation" and "a feature you now have to support forever." Past a certain number of consumers, the distinction stops being yours to draw. The people building on your system are not reading your documentation and choosing to depend only on the promised parts. They are observing what happens and building on that, which is a rational thing to do, and the aggregate of all those rational choices is an interface far wider than the one you signed up to maintain. Keeping the boundary between the two legible is exactly the kind of thing that slips out of a team's memory first and hurts most when it is gone.

Why every observable behavior eventually becomes a contract

The mechanism is simple and unstoppable. A single user depends on one accidental behavior. A thousand users, collectively, depend on nearly all of them, because across a large enough population every observable quirk is useful to someone. The classic illustration is xkcd 1172, "Workflow": a user complains that holding the spacebar now heats their coffee, because they had rigged a script around a bug, and "every change breaks someone's workflow." It is a joke that working maintainers quote with no trace of humor, because it is precisely their life.

The behaviors that get depended on are rarely the ones you would guess. Output ordering is the canonical one, but the list is long. Response timing gets baked into someone's timeout or their assumption that two calls complete in a certain sequence. The exact text of an error message gets parsed by a downstream system that has no other way to distinguish two failure modes, so rewording the message for clarity breaks their error handling. Whether a field is absent, null, or an empty string becomes a de facto flag. Floating-point results that happen to round a certain way, the specific format of an auto-generated ID, the order of keys in a JSON response, the precise set of log lines a process emits: all of it is observable, and observable is all it takes.

None of this happens through anyone's malice or even carelessness. It is the same dynamic that makes the interfaces between systems mirror the communication between the teams that built them: behavior flows across a boundary, someone on the other side comes to rely on it, and now the boundary is wider and stiffer than the diagram said. Hyrum's law is the API-level version of a broader truth about robustness, and it sits directly across from Postel's law. Postel tells the receiver to be liberal in what it accepts. Hyrum tells the sender what that liberality costs on the other end: every tolerance you extend, every behavior you expose, becomes a promise the moment someone notices it.

What your implicit interface actually includes

The most useful thing you can do with Hyrum's law is stop treating it as a proverb and start treating it as a checklist. Before you can decide what is safe to change, you have to know what you are actually exposing. Your implicit interface is almost always larger than your documented one, and the gap is where the surprises live. Here is the inventory the definitional summaries skip, the observable behaviors that most often harden into contracts:

  • Ordering. The sequence of items in any list, array, result set, or serialized object, unless you have deliberately randomized it.
  • Timing and latency. How long a call takes, and the relative timing of calls, which gets encoded into timeouts, retries, and race-prone assumptions.
  • Error text and error shape. The exact wording of messages, the structure of error responses, and which errors are thrown versus returned.
  • Null versus empty versus absent. Whether a missing value comes back as null, an empty collection, a zero, or a missing key entirely.
  • Precision and formatting. Decimal places, date formats, ID formats, trailing whitespace, capitalization, and numeric rounding.
  • Side effects and their order. Which operations write logs, emit events, or touch other systems, and in what sequence.
  • Status codes and metadata. The specific HTTP status, headers, or response metadata returned for a given case, which clients branch on.
  • Concurrency behavior. Whether operations that happen to be safe to run in parallel today are relied upon to stay that way.

The point of the list is not to document every one of these publicly, which would only turn accidental behaviors into promised ones. The point is that you cannot make an informed decision about changing something until you know whether it is observable and whether anyone is likely watching. A behavior you have never even noticed you expose is exactly the one that breaks a customer the day you change it. This is the same reason code cannot document its own intent: the implementation shows what it does, including every accidental thing, but only a deliberate record can say which of those things you actually meant to guarantee.

What you can safely change: a break-or-preserve decision table

Hyrum's law does not mean you can never change anything. If it did, every system would freeze the day it got popular. It means you have to reason about each change instead of assuming the contract protects you. When you are about to alter an observable behavior, the question is not "am I within my documented rights," because you usually are and it does not save you. The question is what the change will actually cost and whether you can afford it. This table is the one the AI summaries and the one-line definitions never give you:

QuestionLean toward changing itLean toward preserving it
How many consumers observe this behavior?Few, or all internal to your teamMany, or unknown external parties
Can you detect who depends on it?Yes, through logs, metrics, or a fleet you controlNo, it is a public API with anonymous callers
Is the dependence likely, given the behavior?Unlikely to be useful to build onObvious to rely on, like output order
How reversible is the change?Easy to roll back within minutesShips to clients you cannot recall
What does a break cost the consumer?A clear error they fix quicklySilent data corruption or a broken workflow
Can you shift the cost with a deprecation window?Yes, you can warn and migrateNo, the change is immediate and global

The pattern under the table is that the danger is not the change itself, it is the combination of many unknown consumers, a behavior that is easy to depend on, and a break that is hard to detect or reverse. When those line up, treat the behavior as a real contract no matter what your documentation says. When they do not, you have more freedom than the folklore suggests. Deciding this deliberately, rather than by reflex or by fear, is exactly the sort of consequential call worth running through an actual decision framework and recording, because the reasoning is the part that will be missing when the question comes up again.

How teams actually fight Hyrum's law

The teams who deal with Hyrum's law at scale do not try to stop people from depending on observable behavior, which is impossible. They make the behavior they do not want to promise either impossible to depend on or cheap to change. The strategies the AI overview names and then declines to explain are these.

Add deliberate entropy. The best-known example is Go, whose runtime randomizes the iteration order of maps on purpose, so that no one can accidentally depend on an order the language never promised. If a behavior must not become a contract, do not let it be stable. Vary it within its allowed range so the only thing observers can rely on is the actual guarantee.

Test your own contract by breaking everything else. Google's Abseil libraries are maintained with tests that deliberately assert only the promised behavior and actively churn the rest, so a change that stays within the contract cannot be blocked by an internal test relying on an accident. Your test suite is itself a consumer, and if it depends on unpromised behavior, it will fight every legitimate change you try to make.

Version and deprecate on purpose. When a behavior has already become load-bearing, the escape is an explicit new version plus a real deprecation window: announce the change, give consumers a migration path, measure who is still on the old behavior, and only then remove it. This turns an un-removable dependency into a scheduled one. It is the same discipline that keeps a full rewrite from silently discarding the contracts the first system accreted, where the second system re-solves problems the first one had quietly guaranteed.

Accept liberally, but loudly. When you must keep tolerating an observed behavior, do it visibly: log it, alert on it, and track it as debt to be driven down, rather than letting it settle into a permanent, invisible feature. The behavior becomes a temporary bridge you are watching, not a fossil you have forgotten.

The through-line is that Hyrum's law is beatable only before the dependence forms. A complex system does not spring into existence with all these implicit contracts; it grows into them from a simpler one that worked, one accidental dependency at a time. The teams that stay changeable are the ones that decided, early and deliberately, which behaviors were promises and which were noise, and then made the noise unreliable.

The contracts nobody wrote down

Here is the part that makes Hyrum's law more than an API-design footnote. An implicit interface is, by definition, the interface nobody documented. The behaviors that become accidental contracts are precisely the ones no one decided on, which means no one recorded the intent behind them. So when the question finally arrives, the year after the person who wrote the code has moved on, there is no way to answer it. Was this ordering deliberate or incidental? Was this error text meant to be stable or was it a throwaway string? Is this null-versus-empty distinction a feature or an accident? The code cannot tell you, because the code only shows behavior, not intent, and the intent has decayed out of everyone's memory.

That decay is what turns Hyrum's law from a manageable risk into a paralyzing one. A team that recorded which behaviors it intended to guarantee can change everything else with confidence. A team that recorded nothing has to treat every observable behavior as sacred, because it cannot distinguish a promise from an accident, and so it freezes. The difference between the two teams is not talent or discipline in the moment of the change. It is whether, at the moment the behavior was created, someone wrote down what it was meant to guarantee.

The fix is cheap and specific: when you build an interface, record its intended contract as deliberately as you build it, and note explicitly what is not promised. That belongs in the spec for the interface, and the consequential "this behavior is guaranteed, this one is incidental" calls belong in a durable decision record. A promise you wrote down is a contract you can defend and evolve. A promise that exists only because the code happens to behave that way is a trap that springs on whoever inherits it. Try Scribelet free and keep the intended contract of your interfaces, and the reasoning behind each guarantee, where the next maintainer will actually find it.

Reading Hyrum's law correctly

The most common misreading is despair: if everything observable becomes a contract, then nothing can ever change, so why try. That is not the lesson. Plenty of systems evolve constantly while serving huge user bases. They do it by shrinking their implicit interface on purpose, through entropy, versioning, and deliberate contracts, so that the set of things people can safely depend on is close to the set of things they actually promised. Hyrum's law is a reason to be intentional, not a reason to freeze.

The opposite misreading is complacency: the belief that writing thorough documentation makes you safe. It does not. Documentation controls your explicit interface, and Hyrum's law is entirely about the implicit one. You can document "order is not guaranteed" in bold and people will still depend on the order, because they are observing behavior, not reading prose. Documentation is necessary and it is not sufficient. What actually protects your freedom to change is making the unpromised behavior unreliable or detectable, so the dependence either never forms or can be found and migrated when it does.

Read correctly, Hyrum's law is a lens for a single recurring question at every boundary you own: of all the behavior this thing exposes, which parts am I willing to guarantee forever, and what am I doing to keep everything else from quietly becoming a guarantee I never agreed to?

Getting started

Hyrum's law is not a rule to obey or a fate to accept. It is a discipline for keeping your systems changeable in the face of people depending on everything they can see.

Start with three moves. Inventory the observable behaviors of your most-used interface using the checklist above, and mark which ones you actually intend to guarantee. For the ones you do not, make them harder to depend on: randomize what can be randomized, and where you cannot, log and watch for reliance so you can catch a dependence while it is still cheap to migrate. And for every real guarantee, write down that it is a guarantee and why, because the single thing that separates a team that can evolve its systems from one that is frozen by them is whether the intended contract was ever recorded. Try Scribelet free and let it hold the contracts your code cannot express, so the behaviors you meant to promise stay promises and the ones you did not never harden into them.

Share this article

We use cookies for analytics to improve your experience. Learn more