Why Good Systems Grow Without Breaking What Already Works
Hatched by Jaeyeol Lee
May 11, 2026
9 min read
9 views
88%
The hidden problem is not change, it is meaning
What if the hardest part of building software is not adding features, but deciding what kinds of change are allowed to exist at all? Most teams treat change as a single category. A request arrives, something is modified, and the system moves on. But systems do not experience change uniformly. Some changes extend the world of the system. Others rewrite the meaning of the world it already has.
That distinction is more important than most engineering discussions admit. In one mode, you are growing. You add a new field, a new signal, a new namespace, a new route, a new capability. In the other mode, you are breaking. You remove a name, reinterpret a value, force existing code to relearn reality. Growth can be absorbed. Breakage demands coordination, migration, and often pain.
This is why so many software disasters are not really performance problems or frontend problems or database problems. They are semantic problems. A system fails when different parts no longer agree on what something means. The database says one thing, the UI assumes another, and the runtime tries to make peace by redoing work everywhere. The deeper challenge is not just moving data or rendering pixels. It is preserving meaning while still allowing the system to evolve.
The central design question in modern software is not, “Can we change it?” It is, “Can we change it without making old truths false?”
That question connects databases, reactivity, and application architecture more tightly than it first appears.
Growth and breakage are different species of change
There is a reason mature systems become conservative about names. A name is never just a label. It is a contract. Once a name exists in production, other codebases begin to depend on it, sometimes in visible ways, sometimes in subtle ones. That dependency is the real production environment: not your laptop, but the network of programs, users, jobs, dashboards, and integrations that have learned the current shape of reality.
This is why adding is fundamentally safer than changing. Adding a field, a signal, or an event creates a new path without invalidating old ones. Changing the meaning of an existing thing, or removing it entirely, is a stronger act. It may be justified, but it is not neutral. It asks every dependent system to renegotiate its assumptions at once.
That is why a useful mental model is to separate change into two families:
- Growth changes: new capabilities, new names, new namespaces, new annotations, new routes, new signals.
- Breakage changes: removals, reinterpretations, renames without aliases, incompatible rewrites.
The first family can often be deployed continuously. The second family needs staged coordination, deprecation, and time. Confusing them is how teams accidentally turn evolution into instability.
This distinction also explains why schema is more than a set of text files in source control. A database can serve as the actual home of meaning because it can validate, store, query, transact, and remember. In other words, it does not merely record what the application thinks. It participates in governing what can exist. If the database is the source of truth, then reproducibility and auditability are not special projects. They fall out naturally from the system’s own machinery.
A subtle but important implication follows: the best schema strategies are historical strategies. They do not just define the present. They preserve a lineage of meanings. That is why metadata such as deprecation flags, namespaces, and annotations matter. They are not bureaucratic extras. They are the system’s memory of how it became what it is.
Reactivity is the runtime version of schema discipline
Now shift from storage to UI. At first, reactive frameworks seem to be about speed. But speed is only the surface. The real question is: how much of the system must be reconsidered when one value changes?
Coarse-grained frameworks take a broad approach. A value changes, and the framework reruns large portions of the tree. It is like asking an entire department to redo its work because one spreadsheet cell changed. This can be acceptable, even elegant, when the cost is small or the structure is simple. But it becomes expensive as complexity grows, because the runtime cannot precisely infer what depends on what.
Fine-grained reactivity is different. A signal acts like a bucket with a traffic cop. When a value is read, the system records the dependency. When the value changes, the framework knows exactly who needs to respond. The point is not just that less work is done. The point is that dependency is made explicit and local.
That idea mirrors the logic of schema growth. If you can add new meaning without invalidating old meaning, you reduce the blast radius of change. If the runtime can know exactly which parts of the interface depend on a value, it can update only those parts instead of treating the whole application as suspect.
Consider a simple counter in a user interface. In a coarse-grained system, changing the count might rerun the counter, the wrapper, the display, and other surrounding components, even when only one small display value actually changed. In a fine-grained system, only the precise consumers of the count are affected. The difference is not just computational efficiency. It is architectural honesty. The system acknowledges what changed and what did not.
Qwik pushes this logic even further with resumability. Instead of hydrating a full application on the client, it can pause on the server and resume on the client without redoing all the work. That is not merely optimization. It is a commitment to continuity. The system refuses to pretend that all context must be rebuilt from scratch.
Svelte and Vue also move in this direction, using compiler help or signal-like primitives to reduce unnecessary recomputation. Different frameworks take different paths, but the underlying principle remains the same: preserve local truth, update only where dependency requires it.
Reactivity, at its best, is not about making the UI faster. It is about making change more precise.
The deeper pattern: systems fail when they forget the shape of dependency
Schema growth and reactive rendering may appear to live in different layers of the stack. One concerns persistent data. The other concerns ephemeral interface updates. Yet they are solving the same problem from opposite sides: how does a system evolve while minimizing unnecessary reinterpretation?
A well designed database schema tells the truth about what exists, what used to exist, and what must continue to mean the same thing. A fine grained runtime tells the truth about what depends on what, and only recomputes what is actually affected. Both are methods for controlling the cost of change.
This suggests a powerful general framework: every system should make its dependencies explicit, local, and inspectable.
If dependencies are hidden, every change becomes scary. Rename a field and something breaks downstream. Update a value and a large section of the UI rerenders. Remove a column and an old job fails silently at 2 a.m. Hidden dependencies create the illusion of simplicity until the first real change.
If dependencies are explicit, change becomes navigable. You can add a new field without renaming the old one. You can introduce a new signal without forcing unrelated components to wake up. You can deprecate safely because the system knows the old name still exists, even if it should not be the default forever.
This is where names become crucial. Names are dependency surfaces. A bad name is not just ugly. It is expensive. It increases the chance that meaning will need to be revised later, and revisions are where systems pay the bill. That is why namespaces matter so much. They let the same local name exist in different contexts without collision. They lower the cost of being wrong. They make evolution less brittle.
Annotations and deprecation flags serve the same purpose. They do not just help humans. They let the system carry history forward. They say: this thing still exists, but this is how to think about it now. In a world obsessed with clean deletion, that may sound like clutter. In practice, it is one of the most important tools for preserving continuity.
A practical principle: optimize for reversible growth, not heroic rewrites
A lot of engineering culture quietly celebrates the dramatic refactor, the clean slate, the total replacement. But systems rarely survive by being rewritten cleanly. They survive by being extended carefully.
That leads to a useful principle: design for reversible growth. If you add a capability, can you keep the old one alive long enough for dependent code to move? If you introduce a new data shape, can you support both shapes during the transition? If you change how state is represented, can old readers and new writers coexist for a while?
This is not just a backend rule. Frontend architecture benefits from the same thinking. If a component depends on a signal, can that dependency be isolated? If a UI tree has to update, can the change remain structural only where necessary? If the framework can avoid re-executing unrelated code, then you have built a system that tolerates growth more gracefully.
A good analogy is city planning. A brittle city assumes every road must be rebuilt whenever a new district opens. A resilient city adds routes, preserves old ones, and labels districts clearly so traffic can adapt without chaos. The city does not deny change. It routes change.
The same is true of software. The best systems do not eliminate complexity. They concentrate it where it belongs. They make the cost of change visible, and they keep meaning stable enough that evolution does not become amnesia.
That is why “never remove a name” is not a weird rule, but a disciplined stance. Removing a name is easy. Preserving a name while changing the system around it is hard. But the hard thing is often the thing that keeps production honest.
Key Takeaways
- Separate growth from breakage. New capabilities can usually be added safely. Renames, removals, and semantic changes need explicit migration planning.
- Treat names as contracts. Once a name is in production, other code depends on its meaning. Prefer new names, aliases, namespaces, and deprecation over silent reuse.
- Make dependency visible. Whether in a database schema or a reactive UI, the system should know what depends on what so it can change locally instead of globally.
- Preserve history in the system itself. Annotations, deprecation flags, queryable schema, and transaction history help software remember how it evolved.
- Design for reversible growth. The safest changes are additions that coexist with old behavior long enough for the ecosystem to adapt.
The real design problem is continuity
The most interesting connection between schema design and reactivity is not technical similarity. It is philosophical. Both are disciplines of continuity. They ask how a system can remain itself while changing, how it can evolve without turning every update into a crisis of interpretation.
That is a much richer goal than mere correctness or performance. Correctness says the system matches the spec now. Performance says it does so efficiently. Continuity says the system can keep its promises across time.
In that light, the best software is not the software that changes fastest. It is the software that changes most honestly. It knows what is new, what is old, what is still valid, and what should never be quietly redefined. It refuses to confuse growth with amnesia.
If you remember only one idea, let it be this: good systems do not survive by avoiding change. They survive by making change legible. When meaning is explicit, dependencies are local, and history is preserved, evolution stops being a threat and becomes the natural shape of the system itself.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣