Fast, Loose, and Fragile
Why the freedom that makes the web powerful also makes it fragile.

The web is like an unlocked car. Anyone can get in and drive, but you still should wear a seatbelt.
The web’s strength has always been its looseness. You can shape it, stretch it, splice it together. It tolerates a degree of chaos that would break other platforms. That flexibility is part of its magic, and part of its curse.
In a native app, code lives behind a wall. You build, sign, release. Every change moves through a visible lifecycle. Some frameworks, like React Native or Flutter, blur that boundary and allow faster iteration. But even there, it is far harder to alter behaviour on the fly. That separation is both a blessing and a curse. It slows you down, but it also protects you from yourself.
On the web, the wall is porous. Code can slip in through a CMS, a tag manager, or a partner integration. It works just enough to be convincing, but not enough to be safe.
It feels fast. You “just add a script”, see it appear, and move on. But that kind of speed comes without scrutiny. Injected code passes through no review, no static analysis, no type checking. It is invisible to test coverage and caching strategies. It sidesteps the release pipeline that exists to protect the player. What looks like agility is usually a form of bypass.
And often, the person adding it is outside the normal product development cycle. They might be working in a CMS, unaware of how far the change will reach or how to verify it. A snippet intended for one brand or locale can quietly spread across environments through shared templates or caching layers. Without review or observability, there is no easy way to know where the code landed or what it did once it got there.
Once those fragments run, they ignore the normal lifecycle of the application. They can attach listeners without removing them, allocate memory without release, or mutate the DOM during paint. Over time, the page starts to falter. Frames drop, transitions shudder, and the experience feels slightly off. Jank is how players describe it. The cause is almost always the same: work that was meant to be temporary has become permanent.
In Watching the Watchers, Part 2, I wrote about how external scripts quietly eat into the frame budget. Each watcher and polling loop competes for the same 16-millisecond window that keeps motion smooth. Injected code does not just slow the interface; it changes its rhythm. Memory leaks deepen the problem, holding references to nodes and listeners long after they should have been released.
You can often see the symptoms in RUM. INP rises, long tasks cluster, heap usage grows. What you cannot easily see is ownership. The script never went through the normal process, never appeared in a changelog, and never had a clear maintainer. By the time dashboards turn red, the code has drifted out of memory and out of mind.
People rarely do this out of carelessness. They are trying to move quickly, to meet a campaign date, to ship something small without the overhead of a full deployment. When the main path feels slow, the side door starts to look reasonable. In truth, these shortcuts are a signal. They point to where process has become friction.
The answer is not to lock everything down. It is to make the right path easier than the wrong one. If a team can propose, review, and deploy change within hours, they will not reach for an injection. If experiments have a safe and visible framework, they can run without leaving debris behind.
The web does not need fewer hands. It needs steadier ones, and systems that help them move quickly without breaking trust.