software architecture

communication

distributed systems

leadership

Why Building Software Is Hard: The Cost of Ambiguity

Software fails in the gaps between goals, communication, system states, and real-world behavior. A founder-focused lesson from a real-time hardware integration.

Building software is difficult for a reason that is easy to miss from outside the engineering team: the code is only the visible part of the system. The harder work is agreeing on what the system is supposed to mean when real people, real devices, real time, and real failure are involved.

For a CEO or founder, this appears as missed dates, estimates that keep moving, expensive rework, and a product that works in a demonstration but not reliably in the field. For a CTO or engineering leader, it appears as ambiguous requirements, conflicting assumptions, difficult integrations, and bugs that cannot be reproduced consistently.

The most expensive bug may be an unanswered question

A software team can write excellent code and still build the wrong thing. The problem is often not incompetence. It is that the team has not made the important decisions explicit.

What does “authorized” mean? Which clock decides whether access is allowed? What happens when a message arrives late? What should a device do when a field is missing? Does “nothing happened” mean success, denial, timeout, or an internal error?

Fred Brooks made a similar point in No Silver Bullet: the difficult part of software is the specification, design, and testing of the conceptual system, not merely representing it in code. In other words, the most expensive ambiguity is often created before the first line is written.

A real integration failure

I recently tested a real-time integration with an edge hardware engineer. The broader product included another application that sent an access request for a building. The authorization policy was complex for the use case, and we had to reason through the messages, payloads, and sequence of events between the systems.

I tested my part locally. The messages looked correct. The payloads looked correct. The local flow appeared to work.

Then the real test failed.

The reason was a rule that had not been visible in my local model: the device checked the hour first. If the request arrived outside the allowed time for that device, it did nothing.

“Nothing” was not a message. It was not an error. It was not a rejected authorization response. It was an absence of observable behavior.

The device was not necessarily broken. My code was not necessarily broken. The test was not necessarily wrong. The system was incomplete in our shared understanding of its behavior.

More conditions create more possible realities

The edge hardware could operate in five or more modes depending on conditions such as time, device state, authorization, connectivity, and the order in which messages arrived. That means the system does not have one simple path. It has a state space.

The number of possible interactions grows quickly when several systems make independent decisions. A local test covers one path through that space. Production visits the paths nobody wrote down.

This is why distributed systems are difficult. A message can be delayed, duplicated, reordered, rejected, or silently ignored. A device can be online according to one component and unavailable according to another. A valid payload can still be invalid in the current state.

Martin Kleppmann’s Designing Data-Intensive Applications is valuable precisely because it treats reliability, consistency, operability, and failure as design concerns rather than afterthoughts. Google’s SRE material makes the same operational point: systems need monitoring and troubleshooting practices that reveal what happened, not just code that works on the happy path.

Communication is part of the architecture

Communication is often treated as a soft skill. In software, it is a technical dependency.

Every undocumented assumption becomes a hidden branch in the system. Every different interpretation of a word like “approved,” “available,” or “processed” becomes a compatibility risk. Every team that owns only one part of the flow can believe its part is correct while the end-to- end behavior is wrong.

Architecture is not only boxes and arrows. It is also the agreement between the people and systems responsible for those boxes. Conway’s Law captures this relationship: the structure of a system tends to reflect the communication structure of the organization that builds it. If teams do not share a clear model of the problem, the architecture will expose that gap.

AI does not remove the need for clarity

AI can help with research, implementation, testing, documentation, and exploring alternatives. But AI generally does what we ask it to do. If the goal is wrong, the instructions are unclear, or the important decisions are left on autopilot, AI can make the wrong direction faster and more convincing.

This is not a reason to avoid AI. It is a reason to improve the system around it. Leaders still need to define the outcome, the constraints, the unacceptable failures, and the point at which a human must review the result. AI can generate code and possibilities; it cannot take responsibility for an unclear business decision.

What leaders should make explicit

Clear communication does not mean producing more documents. It means making the decisions that affect business outcomes visible and testable. Before implementation, a leadership team should be able to answer:

  • What is the business outcome this flow must protect?
  • What are the valid states and transitions?
  • Which conditions change the result?
  • What is the order of operations?
  • What does success, denial, timeout, and failure look like?
  • Which component owns each decision?
  • How will an operator know what happened?
  • What must be deterministic, and where is uncertainty acceptable?

These questions are useful whether the system is a startup MVP, a payment workflow, a machine-learning feature, or an access-control device. They reduce the distance between the business intention and the behavior customers actually experience.

The goal is not perfect certainty

No team can eliminate uncertainty. Requirements change. Hardware has limits. Networks fail. Customers behave in ways nobody predicted.

The goal is to decide where uncertainty is acceptable and where it is dangerous. A recommendation can be probabilistic. A security boundary, authorization decision, billing state, or irreversible data operation usually needs explicit and observable rules.

Good engineering is not the absence of complexity. It is the ability to understand which complexity is essential, which complexity was created by our choices, and which complexity can be removed before it becomes a business problem.

Software succeeds when the shared model succeeds

The lesson from the failed test was not “write better code.” It was “make the system’s behavior explicit.” We needed a shared model of the states, rules, messages, timing, and observable outcomes before another local test could tell us anything useful.

For founders and executives, this is the practical point: software delays are often decision and communication delays in disguise. The fastest teams are not the teams that skip thinking. They are the teams that resolve the right questions early, expose failure clearly, and keep the business goal connected to the technical behavior.

The quality of the code matters. But before code can be good, the team must agree on what “good behavior” means.

Has this happened in your team? A feature looked correct in isolation, but failed when the complete system, its hidden rules, and real-world conditions were involved? That gap is often where the most valuable engineering work begins.

Further reading

This argument connects with Fred Brooks’s No Silver Bullet,Designing Data-Intensive Applicationsby Martin Kleppmann and Chris Riccomini, and Google’sSite Reliability Engineeringbook. For practical engineering habits, The Pragmatic Programmerremains a useful companion.

I’m Arnold Moya. I write about software architecture, AI, systems, performance, and the tradeoffs behind building technology that works in the real world.