Why Good Systems Need More Than One Kind of Truth

‎

Hatched by

Jun 18, 2026

9 min read

62%

0

The hidden question behind every validation tool

What looks like a simple tooling choice often hides a much bigger philosophical question: where should truth live in a system? In code, that question appears when you decide how to validate data. Do you trust types alone, runtime checks alone, or a dedicated schema that sits between the two? The answer matters more than most teams realize, because validation is not just about preventing bugs. It is about deciding what counts as reality in your application.

At first glance, a validation library can seem like a practical convenience, just a way to avoid malformed input and noisy errors. But there is something deeper at stake. In a TypeScript application, compile time can tell you one story, runtime can tell you another, and your users can supply a third. The real challenge is not choosing one over the others. It is designing a system where these stories agree often enough to be useful, while still acknowledging that they will never be identical.

That is why the landscape of tools matters. When multiple libraries exist for the same job, it is tempting to treat them as interchangeable. Yet the existence of alternatives such as Yup, Joi, io-ts, and Zod suggests that validation is not a solved problem. It is a design space. Each tool encodes a different answer to the question of how to express rules, infer types, and keep your code honest when reality arrives at the boundary.

Validation is really a theory of trust

Most teams frame validation as a defensive layer. Data comes in, checks happen, bad inputs get rejected. That is true, but incomplete. Validation is actually a theory of trust: a set of assumptions about which parts of your system can be believed, and when.

Consider a payment form. At compile time, TypeScript can tell you that your function expects a cardNumber string and an amount number. That is useful, but it does not prove the user entered a real card number or a positive amount. The type system protects your code, not your world. Runtime validation steps in because the world is messy, and the assumptions written in code need to be checked against actual inputs.

This is where schemas become more than a utility. A schema is a contract, but it is also a negotiation between intent and reality. It says, in effect: "This is the shape of the world I am willing to accept." The more clearly you can write that contract, the less ambiguity leaks into the rest of the system. Without that clarity, your application becomes a chain of informal expectations, each one slightly different, until a bug reveals that nobody agreed on what valid really meant.

A validation schema is not just a gate. It is a shared language for what your system believes to be true.

This is why the choice of tool matters even when the surface behavior looks similar. Some tools emphasize runtime validation first, some emphasize type inference first, and some try to bridge the two. But the deeper question is not which package has the nicest API. It is which one helps you minimize the distance between your declared intent and the reality your code must survive.

Why one truth is never enough

The temptation in software design is to search for a single source of truth. In validation, that instinct is understandable, but incomplete. A single truth sounds elegant until you remember that truth changes shape depending on the layer.

At compile time, TypeScript excels at expressing structure. It can tell you whether an object has the right fields, whether a function is being called correctly, and whether you are misusing a value that should be narrowed first. But TypeScript does not see the network, the browser, the database, or the user. It cannot stop a malformed JSON payload from arriving, nor can it protect you from data that is technically well typed but semantically wrong.

At runtime, schemas catch what types cannot. They can reject an empty string where a nonempty identifier is required. They can transform inputs, coerce values, and surface meaningful error messages. Yet runtime checks alone can become repetitive and brittle if they are disconnected from the rest of the codebase. You can validate something perfectly at the edge and still accidentally misuse it internally unless your types reflect the validated shape.

This is the real tension: types are promises to the compiler, schemas are promises to the world. If those promises are disconnected, the system becomes fragile in subtle ways. If they are aligned, you get something powerful: code that is easier to reason about, safer to refactor, and less dependent on the memory of whoever wrote the last handler.

A useful mental model is to think in terms of three layers of truth:

  1. Declared truth: what the code says should exist.
  2. Observed truth: what the input actually contains.
  3. Operational truth: what the rest of the system can safely rely on after checks and transformations.

Most bugs happen when teams confuse one layer for another. A value may be declared as valid, observed as malformed, and then treated operationally as trustworthy anyway. Good validation tools reduce that gap, but they do not eliminate it. What they do is make the transition between layers explicit.

The real value of a schema is not safety, it is alignment

It is easy to sell validation as a safety feature. That is true, but safety is only half the story. The deeper benefit is alignment: between developers, between runtime and compile time, and between the shape of incoming data and the shape of the code that consumes it.

Imagine a team building a shipping application. An order can arrive from a web form, a mobile client, or an external API. Each source may describe an address differently. One might send zipCode, another postalCode, another a single freeform address string. Without a shared schema, every service has to make private assumptions about how to interpret those differences. Small mismatches accumulate into expensive failures, like failed deliveries or corrupted records.

A schema forces the team to answer questions that otherwise remain hidden. Is an apartment number required? Is a phone number optional? Should empty strings be converted to undefined or rejected outright? Those decisions matter, because they determine how downstream logic behaves. When expressed clearly, they become part of the architecture rather than incidental details scattered across form handlers and controllers.

This is where modern validation tools can be especially valuable. A tool that helps define a schema once and infer types from it does more than reduce duplication. It turns the schema into a coordination point. The runtime check and the static type are no longer separate artifacts drifting apart. They are two views of the same contract.

That does not mean every tool should be replaced by the same approach. Different libraries optimize for different tradeoffs. Some are more declarative, some more flexible, some more opinionated about transformation, some more ergonomic for existing ecosystems. But that variety itself reveals something important: validation is not merely about rejecting bad data. It is about making the structure of trust visible, stable, and reusable.

The deeper design lesson: systems fail at the boundary

If you zoom out, validation is one instance of a broader rule in software design: systems fail at their boundaries before they fail at their cores. The internal logic of an application can be beautifully typed, elegantly modular, and still collapse if the boundary layer is vague. That is why schemas matter so much. They are not peripheral. They are where the application meets the world.

Think of a schema like the customs checkpoint between countries. Inside the country, people can assume the rules. At the border, the system has to inspect passports, declarations, and prohibited items. The checkpoint is not where the culture lives, but it is where the culture is protected. Similarly, validation is not the heart of your business logic, but it protects the heart by ensuring only properly interpreted data reaches it.

The stronger insight here is that good boundaries do not only block bad inputs, they shape good internal design. Once your application receives reliably structured data, your functions become smaller, your branches disappear, and your domain logic becomes easier to read. You no longer need to ask, in every layer, whether age is a string or a number, whether email exists, or whether a value is null because nobody checked it earlier. The schema has already answered those questions.

This changes how teams should think about validation libraries. The question is not "Which one validates data?" Almost all of them do. The better question is "Which one lets the boundary become a first-class part of the architecture?" That answer depends on ergonomics, inference, transformations, composability, and developer discipline. But the underlying principle remains the same: the boundary is where complexity should be concentrated, not spread everywhere else.

Key Takeaways

  • Treat validation as a contract, not a convenience. A schema is a formal statement of what your system considers acceptable.
  • Separate declared truth from observed truth. TypeScript types describe intent, runtime schemas verify reality.
  • Prefer tools that reduce drift. The less your schema and types diverge, the safer and easier your code becomes to evolve.
  • Design boundaries intentionally. Put complexity at the edge of the system so the interior stays simple and trustworthy.
  • Use validation to create alignment across the team. A shared schema prevents hidden assumptions from multiplying across forms, APIs, and services.

Conclusion: the best systems are honest about uncertainty

The most important lesson in validation is not that data can be checked. It is that systems are never as certain as they pretend to be. Users, networks, APIs, databases, and integrations all introduce ambiguity. Good engineering does not deny that uncertainty. It creates structures that make uncertainty manageable.

That is why the existence of multiple validation tools is not a sign of confusion. It is a reminder that there are many ways to represent trust, and each one emphasizes a different part of the problem. The mature move is not to search for a magical tool that erases the need for judgment. It is to understand that schemas, types, and runtime checks are complementary expressions of a deeper goal: turning messy reality into dependable software.

Once you see validation this way, the question changes. You are no longer asking which library is best in the abstract. You are asking what kind of truth your system needs, where that truth should live, and how explicitly you want to draw the line between what is assumed and what is actually known. That is not just a coding decision. It is an architectural philosophy.

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 🐣
Why Good Systems Need More Than One Kind of Truth | Glasp