Why Software Engineering Isn't Engineering
We borrowed the name to sound respectable. The work itself has more in common with urban planning and jazz improvisation than bridge building.
Three years ago, I watched a civil engineer explain why a bridge couldn’t be built the way the client wanted. The math was clear: given the span, the load requirements, and the materials available, certain constraints were non-negotiable. Physics doesn’t negotiate. The engineer showed calculations, referenced building codes, pointed to decades of established practice. The client grumbled but accepted it.
On a project years back, a product manager asked me to build a feature that would require rewriting our entire authentication system. I said it would take three months. They said we needed it in two weeks for a client demo. I explained the technical constraints. They explained the business constraints. We compromised on a hack that would work for the demo but would need to be rebuilt later. Five years on, they’re still running on that hack. Every new engineer who touches it asks which moron built it this way.
The difference between these two scenarios isn’t about deadlines or difficult clients. It’s about the fundamental nature of the work. Civil engineering operates within physical laws that don’t budge. Software engineering operates in a world where the constraints themselves are up for debate, where “impossible” usually means “expensive” or “risky,” and where what you build today will be different from what you need tomorrow.
The Constraints We Don’t Have
Traditional engineering disciplines have something software engineering lacks: immutable constraints. A bridge must support its weight plus traffic. The calculations are deterministic. The materials have known properties. The laws of physics don’t update on a quarterly release cycle.
Software has no such anchor. The constraints we work with are almost entirely human-made. Programming languages, frameworks, databases, protocols: all choices, not laws. They change. They get deprecated. They get replaced by something shinier. The “physics” of software is more like the physics of fashion: what works today might be embarrassing tomorrow, not because it stopped being correct, but because the context shifted underneath you.
Fred Brooks identified this decades ago in “No Silver Bullet”. He distinguished between *accidental complexity* (the difficulty we create for ourselves through our tools and methods) and *essential complexity* (the inherent difficulty of the problem itself). In traditional engineering, most complexity is essential. A cantilever bridge is hard because physics is hard. In software, we’ve spent decades reducing accidental complexity, but the essential complexity remains stubbornly high because it’s tied to human needs, which are messy and shifting and contradictory.
When a civil engineer designs a bridge, they’re solving a problem that’s been solved thousands of times. The principles are established. The failure modes are catalogued. When a software engineer builds a system, they’re often solving a problem that’s never been solved quite this way, for these particular users, with these particular constraints. The principles exist, but their application is always novel.
This shows up in how we think about quality. Traditional engineering codifies it. Building codes exist because collapsed bridges kill people, and we’ve agreed as a society that this is bad. In software engineering, quality is negotiable. The consequences of failure are often delayed and distributed. A bug might cause a data breach months later. Poor architecture might strangle development velocity for years. Technical debt compounds invisibly until one day everything is on fire and nobody knows why.
We estimate in story points because admitting we’re guessing in hours feels unprofessional. We create elaborate rituals to manage uncertainty, then act surprised when the uncertainty remains. We hold retrospectives to discuss why we missed our estimates, then miss the next ones by roughly the same margin. The right decision for next week’s deadline is often the wrong decision for next year’s velocity.
Managing this tension is the actual work of software engineering. It’s not about writing perfect code. It’s about making reasonable decisions under uncertainty, knowing that some of them will be wrong, and building systems that can absorb those mistakes without catastrophic failure.
Programming Integrated Over Time
Google’s definition of software engineering is elegant: “programming integrated over time”. The key word is time**. Programming is writing code to solve a problem. Software engineering is writing code that will continue to solve problems as the world changes around it.
Consider code lifespan. A script that runs once faces different demands than a system expected to operate for decades. Over those decades, dependencies evolve. Libraries update. Operating systems shift. Hardware transforms. Languages mature or die. Business requirements pivot entirely. The team that built it leaves. The team that maintains it operates with different assumptions, different tools, different pressures. The code persists, a monument to decisions made by people who are no longer around to explain them.
Traditional engineering builds for stability. A bridge is designed to stand for a century with minimal intervention. Software engineering builds for change. A system is designed to evolve continuously or become that dreaded thing: legacy code that actively hinders progress while remaining too expensive to replace.
This creates what Google calls *sustainability*: the capability to react to valuable change throughout your software’s expected lifetime. You might choose not to upgrade a dependency today, but you must remain capable of doing so. Betting that change won’t become critical might work short-term. Over multiple decades, it probably won’t. The systems that survive aren’t the ones that never change; they’re the ones that can change without breaking.
The difference shows up in how we think about “done.” A bridge is done when it’s built and passes inspection. Software is never done. It’s deployed, maintained, extended, refactored, migrated, deprecated, resurrected, and eventually replaced. The work of software engineering isn’t building something once. It’s creating conditions for continuous building.
This makes software engineering more like scientific research than manufacturing. You’re exploring unknown territory, forming hypotheses, testing them, learning, iterating. The difference is that you’re doing this while simultaneously delivering value, and the experiments become production systems that real people depend on. In research, failed experiments are valuable data points. In software, failed experiments are technical debt with compound interest.
The Coordination Problem
Software engineering is fundamentally collaborative in a way that traditional engineering rarely is. An early definition captured this idea as “the multiperson development of multiversion programs.” A single programmer can write code. Teams of programmers create systems that must survive beyond any individual contributor’s tenure.
This introduces problems that don’t exist in pure programming. Communication overhead. Knowledge distribution. Coordination complexity. Conflicting assumptions. The famous observation from The Mythical Man-Month, that adding people to a late project makes it later, exists because software engineering is as much about human coordination as it is about code. Brooks noted this in 1975. We’re still learning the lesson.
In traditional engineering, teams coordinate around a shared specification. The spec might change, but it’s a document everyone can reference. In software engineering, the specification is often incomplete, contradictory, or changing faster than anyone can write it down. The real specification lives in people’s heads, in Slack threads, in code comments, in tribal knowledge that walks out the door when someone leaves for a startup with better stock options.
This resembles urban planning more than bridge building. You’re not just designing a structure. You’re designing a system that multiple people will modify over time, often without consulting you, frequently in ways you didn’t anticipate. You’re making decisions whose full consequences won’t be visible for years. You’re creating constraints and affordances that shape how future work happens, for better or worse.
The code you write today becomes part of the environment that future engineers work within. Bad decisions compound. Good decisions enable future good decisions. But you rarely know which is which until much later. Like a city planner who builds a highway that becomes a blight or a park that becomes beloved, you’re shaping territory you won’t inhabit.
Software estimation is famously terrible. We’re consistently wrong, often by orders of magnitude. This isn’t because we’re bad at math. It’s because we’re estimating work that’s never been done before, using tools that change, for requirements that shift, with teams whose composition fluctuates. The uncertainty isn’t bounded. It’s inherent. Every software project is to some degree a research project disguised as a construction project.
What We’re Actually Doing
If software engineering isn’t engineering in the traditional sense, what is it?
It’s a discipline of managing complexity across time through people and systems. Part architecture, part organizational psychology, part economics. It’s about making decisions whose full consequences won’t be visible for years. It’s about building systems that enable future building. It’s about creating conditions where teams can work effectively even when you’re not there.
It’s closer to craft than engineering. The principles exist, but their application requires judgment, experience, and something like intuition. The best practitioners develop a feel for what will work, what will cause problems, what tradeoffs are worth making. This knowledge is hard to codify because it’s contextual and temporal. What worked last year might not work now. What works here might not work there.
But it’s also uniquely its own thing. No other discipline combines technical depth, organizational complexity, temporal uncertainty, and human factors in quite the same way. Software engineering is software engineering, not because it resembles something else, but because it resembles nothing else.
Accepting this means we stop expecting engineering-like predictability. Estimation will remain imprecise. Requirements will continue shifting. Technical debt will accumulate. These aren’t failures of process. They’re characteristics of the work itself.
The best software engineers aren’t just good at writing code. They’re good at reading situations, making tradeoffs, communicating clearly, building systems that enable other people to do good work. These are human skills as much as technical skills.
The Name We Borrowed
Here’s what I keep wondering: if software engineering isn’t engineering, should we call it something else?
The term was coined at a NATO conference in 1968, when computer scientists gathered to address what they called the “software crisis.” They hoped that borrowing the language of engineering would bring rigor and predictability to a field that seemed chaotic. More than fifty years later, we’re still using the name, still hoping for that rigor, still operating in a world where the work resists the engineering metaphor.
When we call ourselves engineers, we’re claiming a lineage and a set of expectations. Some of those fit. Some don’t. The mismatch creates confusion for us, for our managers, for the people who depend on our work. We borrow the prestige of engineering while avoiding its constraints. We want the respect that comes with building bridges, but we operate in a world where bridges can be rebuilt overnight and where the laws of physics are suggestions we can argue with.
Maybe the name doesn’t matter. Maybe what matters is understanding what we’re actually doing.
We’re not building bridges. We’re writing books that get rewritten by committees, in languages that evolve, for readers whose needs change before the ink dries. The story never ends. The manuscript is never final.
That’s not a bug.
That’s the nature of the work.


Great piece, excellent references. Reminds me of this I found on HN
https://codemanship.wordpress.com/2025/11/07/is-software-the-ufology-of-engineering-disciplines/