Why Software Engineering Isn't Engineering
The discipline we practice has more in common with urban planning and organizational psychology 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. The bridge would be built according to the laws of physics, not wishful thinking.
In one of my old project teams, 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 later, 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 just about deadlines or difficult clients. It's about the fundamental nature of the work. Civil engineering operates within physical laws that don't change. Software engineering operates within a system where the constraints themselves are negotiable, where "impossible" often means "expensive" or "risky," and where the thing you build today will be different from the thing 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: these are all choices, not laws. They change. They get deprecated. They get replaced. The "physics" of software is more like the physics of fashion: what works today might be embarrassing tomorrow, not because it's wrong, but because the context shifted.
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. 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 changing.
When a civil engineer designs a bridge, they're solving a problem that's been solved before. The principles are established. The failure modes are understood. When a software engineer builds a system, they're often solving a problem that's never been solved in exactly this way, for exactly these users, with exactly these constraints. The principles exist, but their application is always novel.
This shows up in how we think about quality. Traditional engineering codifies quality. Building codes exist because collapsed bridges kill people. The standards reflect that. 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 slow development for years. Technical debt compounds invisibly until it becomes a crisis.
We estimate in story points because admitting we're guessing in hours feels unprofessional. We create elaborate processes to manage uncertainty, then act surprised when the uncertainty remains. The right decision for next week's deadline is often the wrong decision for next year's velocity. The right decision for this quarter's metrics is often the wrong decision for the system's sustainability. Managing this tension is the real 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 accommodate 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 multiple decades, dependencies change. Libraries update. Operating systems shift. Hardware evolves. Languages mature or die. Business requirements pivot. The team that built it leaves. The team that maintains it has different skills and assumptions.
Traditional engineering builds for stability. A bridge is designed to stand for a century with minimal change. Software engineering builds for change. A system is designed to evolve continuously or become legacy code that actively hinders progress.
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, but over multiple decades, it probably won't.
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, 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, and iterating. The difference is that you're doing this while also delivering value, and the experiments become production systems that real people depend on. In research, failed experiments are valuable data. In software, failed experiments are technical debt.
The Coordination Problem
Software engineering is fundamentally collaborative in a way that traditional engineering often isn't. An early definition captured this: "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 "Mythical Man-Month" observation, 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.
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 it can be written down. The real specification lives in people's heads, in Slack conversations, in code comments, in tribal knowledge that walks out the door when someone leaves.
Seems more like urban planning than bridge building. You're not just designing a structure. You're designing a system that multiple people will modify over time, often without your input. 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.
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.
Software engineering 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. You're discovering what's possible, what's needed, and what the actual constraints are, all while building.
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. It's 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 intuition. The best practitioners develop a feel for what will work, what will cause problems, and what tradeoffs are worth making. This knowledge is hard to codify because it's contextual and temporal.
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's like something else, but because it's unlike anything else.
If we accept this, 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.
We focus on what actually matters: creating systems that can evolve, teams that can collaborate effectively, and organizations that can make reasonable decisions under uncertainty. The code matters, but it's not the only thing that matters. The architecture matters, but so does the team structure. The technology matters, but so does the culture.
We recognize that the best software engineers aren't just good at writing code. They're good at reading situations, making tradeoffs, communicating clearly, and building systems that enable other people to do good work. These are human skills, not technical skills.
We stop trying to make software engineering more like traditional engineering. The attempts to add more process, more documentation, more structure: these often make things worse because they assume a predictability that doesn't exist. Instead, we embrace the uncertainty and build systems that can handle it.
The Name Question
Here's what I'm left wondering: if software engineering isn't engineering, should we call it something else?
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.
Maybe we shouldn't call it engineering. Maybe we should call it what it is: the practice of building systems that change, with people who change, for purposes that change, using tools that change.
But names are sticky. The term "software engineering" 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. 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.
The question isn't whether we should rename it. The question is whether we understand 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. The story never ends. The manuscript is never final.
And that's not a bug.
It's the nature of the work.