The Hidden Cost of Not Making Things Real

Jaeyeol Lee

Hatched by Jaeyeol Lee

May 22, 2026

10 min read

62%

0

The Systems We Build Are Judged by Their Shadows

What if the hardest part of building software is not adding features, but deciding what counts as real?

That question sounds philosophical until you hit the practical consequences. A record that is not really deleted still affects queries. A component that is not really on the server still affects the shape of your data flow. In both cases, systems become confusing when they rely on implicit states that preserve convenience while eroding clarity.

Software teams often accumulate these half measures because they feel safe. Deleting something softly seems reversible. Moving a UI pattern into a server side boundary seems elegant without being drastic. But these choices introduce a deeper problem: they create a world where the codebase no longer reflects the actual lifecycle of things. The system starts to depend on ghosts.

The core tension is not about deletion or rendering alone. It is about whether your architecture is willing to make reality explicit, even when that costs short term comfort.


Softness Is Not the Same as Safety

Soft delete looks harmless. Instead of removing a row, you mark it as deleted and keep it around. That seems operationally wise, especially when product managers fear data loss or support teams want a paper trail. The trouble is that a soft deleted thing is no longer a normal thing, but it still occupies the same space in your schema, your queries, and often your mental model.

That split between appearance and state is where complexity begins.

Imagine a library that never throws out returned books, it just stamps them as “not active” and leaves them on the shelf. At first, this gives you flexibility. You can recover old books, audit history, and avoid accidental loss. But over time, every librarian must remember which books are active, which are archived, which are partially hidden, and which should never be offered to patrons. The shelf becomes misleading. The catalog becomes conditional. The building now contains both the collection and its debris.

Soft delete turns a simple lifecycle into a permanent exception. And exceptions, when repeated enough, become infrastructure.

The danger is not that soft delete exists. The danger is that it teaches the system to treat absence as a special case instead of a first class fact.

This matters because many software systems fail less from missing features than from the accumulation of semantic ambiguity. If a row can be deleted but not actually gone, then every consumer of that row must remember the hidden rule. That includes developers, reporting jobs, search indexes, analytics pipelines, and admin tools. The “deleted” flag becomes a tiny landmine embedded everywhere.

A good model of software is not one where nothing can ever be recovered. A good model is one where the difference between current state and historical record is explicit. The problem with soft delete is that it blurs that boundary instead of preserving it.


The Same Problem Appears in UI Architecture

A similar tension shows up in the move toward server components, especially when they are used without the comforting structure of a full framework.

On the surface, server components promise a cleaner division of labor. Put rendering work on the server, ship less JavaScript, keep secrets off the client, and simplify the browser. That sounds like moving logic to where it belongs. But if the boundary is not made concrete, the result can be another kind of softness, a rendering model that is technically sophisticated but operationally slippery.

The deeper issue is not server side rendering versus client side rendering. It is whether your application has a truthful boundary between data, computation, and interaction.

A framework usually gives you guardrails. It tells you where code runs, how data is fetched, what gets serialized, and how hydration works. Remove the framework and the freedom is intoxicating. You can compose server and client behavior yourself, but now every edge case becomes your responsibility. Which component can access what? What happens when data crosses the boundary? Which parts of the tree are static, dynamic, or interactive? The elegance of the idea depends on the discipline of the implementation.

This is where the analogy with soft delete becomes more than a coincidence. In both cases, a system invites you to preserve the convenience of a unified model while quietly relying on hidden metadata to distinguish one state from another. A deleted record is still in the table. A server component is still part of the UI tree. The system says, “Trust me, I know what this really means.”

But software that asks for trust in place of clarity tends to grow expensive.

Think of an airport with one set of signs for both arriving and departing passengers, plus a few subtle color codes to explain the difference. It works if everyone already knows the trick. It fails the moment a new traveler, contractor, or system needs to navigate it without prior knowledge. The more advanced the system becomes, the more dangerous its hidden conventions are.


Explicit Boundaries Are Not Bureaucracy, They Are Compression

The strongest pattern connecting these ideas is this: a system becomes simpler when reality is separated into distinct states that can be reasoned about independently.

This sounds counterintuitive because separation often feels like extra machinery. Why create separate tables for active and archived records when a single flag will do? Why define clear server and client boundaries when a flexible tree seems more powerful? The answer is that apparent simplicity often hides cognitive compression debt. You are packing multiple meanings into one object and borrowing against future understanding.

Here is a useful mental model:

A domain model should minimize semantic aliasing.

Semantic aliasing happens when one structure is forced to represent multiple realities at once. A soft deleted row is both present and absent. A loosely defined server component is both local and remote. The code looks compact, but the meaning is split across conventions, comments, and tribal knowledge. Every extra meaning embedded in one place creates more chances for drift.

A cleaner system does the opposite. It makes each state legible and narrow:

  1. Current data is stored as current data.
  2. Historical data is stored as historical data.
  3. Interactive UI is explicitly client side.
  4. Server generated UI is explicitly server side.

This is not purism. It is a refusal to collapse distinct responsibilities into one ambiguous bucket.

Consider financial systems. They rarely keep “deleted transactions” as ordinary rows with a flag, because financial truth depends on immutability, auditability, and reconciliability. Instead, they model reversals, adjustments, and ledger entries as first class events. The system does not pretend a transaction was never there. It records what happened in a way that preserves both truth and process.

That same discipline applies to product software. If a user account is no longer active, should it be deleted, archived, anonymized, suspended, or retained for compliance? Those are not mere implementation details. They are distinct truths. A good design surfaces them instead of hiding them behind a boolean.

The same principle applies to modern UI architecture. If some components need browser APIs, event handlers, or local state, that is a different kind of thing from components that purely transform data on the server. Treating them as interchangeable just because they can be composed in one tree is like storing archived records and live records in the same bucket because a flag exists.

The elegance comes not from pretending differences do not matter, but from making them precise.


The Real Tradeoff Is Between Convenience and Truthfulness

Most technical debates frame the tradeoff as performance versus maintainability, or flexibility versus complexity. A more revealing framing is convenience versus truthfulness.

Convenience says, keep it in one table, keep it in one tree, keep it easy to undo. Truthfulness says, model what is actually happening, even if it requires more structure.

Convenience is seductive because it reduces immediate friction. One schema. One code path. One component abstraction. But convenience can become a tax collector that returns later in subtler forms: hard to debug bugs, strange authorization leaks, broken analytics, and off by one mental models. The system still works, but only if everyone constantly remembers what the model is pretending.

Truthfulness costs more at design time, but it pays back in operational clarity. It reduces the number of hidden rules. It makes onboarding easier. It makes invariants inspectable. It allows tools to work with the architecture instead of against it.

Here is a practical rule of thumb:

If a system requires repeated “except when” explanations, it probably contains a hidden state that deserves a first class name.

Soft delete often creates such explanations:

  • This item is deleted, except when an admin restores it.
  • It should not appear in search, except when viewing audit history.
  • It should not be used in analytics, except when measuring churn.

Likewise, an under specified server component setup produces its own caveats:

  • This renders on the server, except when it needs interaction.
  • This code stays off the client, except when it doesn’t.
  • This module can be shared, except when it imports browser specific behavior.

Each “except when” is a signal. It means your conceptual model has outgrown its container.

The goal is not to eliminate exceptions. The goal is to stop pretending they are normal.


What Good Design Looks Like When It Refuses to Lie

The best systems do not merely support change. They make change legible.

For data models, that usually means separating lifecycle stages. A user can be active, suspended, archived, anonymized, or deleted, but those states should be explicit and domain driven rather than collapsed into one flag. If recovery matters, make it a separate process. If compliance matters, store the history in a structure designed for history. If deletion is final, let deletion mean final.

For UI architecture, it means defining boundaries that reflect how computation actually happens. Server generated markup, client interactivity, and shared logic should not be blended just because a clever abstraction can hide the difference. The cleaner path is to let the server do what it does best, let the client do what it does best, and expose the seam honestly.

This is one reason frameworks are valuable. At their best, they do not just reduce boilerplate. They enforce truthfulness by making the boundary hard to ignore. Without that boundary, sophisticated systems can devolve into local optimizations that look elegant in isolation but create confusion at scale.

The lesson is not to avoid abstraction. It is to choose abstractions that preserve ontology, meaning the structure of what things are. Bad abstractions compress too much and leave you with a fog of special cases. Good abstractions compress without distortion.

A useful test is to ask:

  • Can a new developer tell what is current, historical, interactive, or server bound without reading ten hidden conventions?
  • Can a query, component, or tool operate correctly without memorizing exceptions?
  • Can the system explain itself through structure, not just documentation?

If the answer is no, the design may be too soft.


Key Takeaways

  1. Prefer explicit states over hidden flags. If something can be active, archived, deleted, or suspended, name those states clearly instead of smuggling multiple realities into one field.

  2. Use boundaries to reduce ambiguity, not just to organize code. Whether in data modeling or UI architecture, a clean boundary makes the system easier to query, test, and reason about.

  3. Treat repeated exceptions as design feedback. If you keep saying “except when,” your model probably needs a new concept, not another workaround.

  4. Separate current truth from historical record. Do not force one structure to serve both purposes unless the domain truly demands it.

  5. Optimize for legibility under change. A good architecture is one that remains understandable when new developers, new features, and new edge cases arrive.


The Most Durable Systems Tell the Truth Early

There is a temptation in software to preserve optionality by making everything reversible, unified, or vaguely defined. Keep the row. Keep the component. Keep the door open. But optionality has a cost when it is purchased by blurring the difference between states that ought to remain distinct.

What lasts is not the system that hides its seams best. It is the system that places its seams where reality already has them.

That is the deeper lesson connecting data lifecycle and rendering boundaries. A software system becomes more robust when it stops trying to make absence look like presence, or server behavior look like client behavior, or historical truth look like live state. The act of making things explicit is not merely architectural hygiene. It is a form of honesty.

And honesty, in software, is not aesthetic. It is scalable.

Sources

← Back to Library

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 🐣