cd /news/artificial-intelligence/software-engineering-is-about-managi… · home topics artificial-intelligence article
[ARTICLE · art-113159] src=hack8s.com ↗ pub= topic=artificial-intelligence verified=true sentiment=· neutral

Software engineering is about managing complexity

Software engineering is fundamentally about managing complexity and making architectural tradeoffs, not just writing code, according to a new essay. The piece argues that while AI excels at generating code, it cannot replace the human judgment required to choose the right architecture, as the same feature can have vastly different correct solutions depending on company size, team, infrastructure, and business constraints.

read13 min views1 publishedAug 27, 2026

There is a misunderstanding about software engineering that AI is making increasingly visible: we tend to confuse writing code with building software.

There is some overlap in certain aspects, but they’re not the same thing.

Writing code means translating an idea into instructions a computer can execute. Building software means deciding which instructions should exist in the first place, how they should interact, which constraints matter, what’s the cost of the decisions, which tradeoffs can be considered acceptable, and how the resulting system can evolve without collapsing under its own constraints and limitations.

Let’s start from a basic premise: AI is an essential tool because it is remarkably good at the first problem.

The second is where software engineering actually begins.

The difficult part was never typing the code #

Consider a relatively ordinary engineering requirement.

We need to process incoming events and update some data.

And these are some of the first questions that arise during a technical discussion:

  • Should we process them synchronously?
  • Should we put them into a queue?
  • Do we need exactly-once processing, or is at-least-once sufficient?
  • Can the system tolerate eventual consistency?
  • What happens when processing fails halfway through?
  • Should we retry? How many times?
  • What happens if the consumer is unavailable for three hours?
  • Do events need to preserve ordering?
  • How much traffic do we expect today? What about two years from now?
  • What are the consequences if an event is processed twice?

… and so on.

These questions have very little to do with syntax.

Programming language’s choice matters, because affects team’s fluency, team’s performance, system’s performance, safety, maintainability, tooling, and operational characteristics, but it does not answer the fundamental questions.

The difficult part is choosing the architecture that represents the right set of compromises.

And there is rarely a universally correct answer.

1 problem can have N completely different correct solutions #

This is particularly obvious when software exists inside a business.

Imagine two companies asking their engineering teams to build what sounds like exactly the same feature.

Their requirements may look identical on paper.

But:

  • Company A has 500 users while Company B has 20 million.
  • Company A may have three engineers maintaining the system. Company B may have 200.
  • One may require strong consistency because mistakes have serious financial consequences. The other may happily accept eventual consistency in exchange for availability and throughput.
  • One company may need to ship in three weeks.
  • Another may expect the system to remain operational for fifteen years.
  • One may already have Kafka, Kubernetes, PostgreSQL, observability infrastructure, and engineers experienced with distributed systems. Another may have a single application server and a PostgreSQL database maintained by four developers.

The technically impressive solution for one company could be an irresponsible solution for the other.

This is why architecture cannot be reduced to asking:

What is the best way to implement X?

The correct question is usually closer to:

Given these constraints, this team, this business, this infrastructure, this budget, these risks, and the expected evolution of the product, what is the most appropriate way to implement X, today?

That is a radically different question.

AI generates solutions. Engineers own tradeoffs. #

This distinction is becoming increasingly important because of how AI is being adopted by software organizations.

AI is extraordinarily useful for software development. We use it as an accelerator: generating boilerplate, exploring APIs, proposing implementations, finding potential bugs, explaining unfamiliar code, producing tests, comparing approaches, or simply reducing the amount of mechanical work required to turn an idea into working code.

But there is a dangerous tendency to extend this capability into something much broader:

delegating engineering judgment itself

You can:

  • give an AI model a requirement and ask it to design the system, “no mistakes”: It will design one.
  • ask it to choose a database: It will choose one.
  • ask whether you should introduce a queue, microservices, caching, CQRS, event sourcing, Kubernetes, Redis, or another abstraction: It will give you an answer.

The existence of an answer, however, does not mean the underlying engineering problem has been solved.

The real problem is that the correct decision depends on context, often an enormous amount of context, that needs hours, days, or even weeks to analyze, understand, and evaluate, often involving multiple departments within the same organization, and frequently leaving gray areas that are difficult to formalize and may become a challenge when future changes are required.

This is how real world works: some of that context exists in documentation. Much of it does not.

It exists in conversations with customers. In the history of the product. In the skills of the engineering team. In operational incidents from three years ago. In budget constraints. In deadlines. In contractual obligations. In the weird behavior of a legacy system nobody wants to touch. In knowing that a customer is likely to request a particular feature six months from now.

And sometimes it exists simply in experience: recognizing that a theoretically elegant architecture will become an operational nightmare for the team expected to maintain it.

You cannot treat all of this as a small detail that will somehow be captured by adding another paragraph to an AI prompt.

There is no architecture without tradeoffs #

Engineering is largely the discipline of deciding which problems you are willing to have. Even if you don’t work at FAANG scale, your projects may still need to process enough data that making the right engineering decisions becomes essential to maintaining adequate performance, without having to throw expensive hardware at systemic inefficiencies.

Even “simple” choices come with factors that need to be considered:

  • Normalize the database aggressively, and you may gain integrity while making certain queries more complicated.
  • Denormalize it, and you may improve read performance while increasing synchronization complexity.
  • Add caching, and you reduce latency while introducing invalidation problems.
  • Introduce asynchronous processing and you decouple components while creating new failure modes.
  • Split a monolith into microservices and you gain independent deployment boundaries while introducing distributed-system complexity.
  • Optimize aggressively and you may gain performance while sacrificing readability.
  • Abstract aggressively and you may reduce duplication while making the system harder to understand.
  • Avoid abstraction and the opposite problem eventually appears.

None of these can be evaluated independently. Every optimization spends complexity somewhere. The job of an engineer is therefore not simply to make the system work.

It is to decide where complexity belongs.

Algorithmic thinking matters more, not less #

This is why I think algorithmic thinking becomes more important in the age of AI, not less.

And by algorithmic thinking I do not simply mean knowing textbook algorithms or being able to implement a red-black tree from memory.

By algorithmic thinking, I mean defining a set of basic rules to follow and applying everyday. I’ve put together a ten-point set of principles:

  • Decompose the problem.
  • Identify invariants.
  • Understand data flows.
  • Choose appropriate data structures.
  • Reason about time and space complexity.
  • Identify contention.
  • Understand failure modes.
  • Recognize which operations are expensive and which ones actually matter.
  • Separate essential complexity from accidental complexity.
  • Understand what should happen when assumptions stop being true.

I know, in the time of boosted productivity, this can sounds weird. AI can generate ten implementations in the time it previously took an engineer to write one. The challenge is to use AI as a tool to apply these principles through targeted, controlled iterations, while keeping a human in the loop.

That makes choosing the right implementation ten times more important. This is what boosted productivity really means.

Your programming language is a tool, not an identity #

The same applies to programming languages.

There is value in choosing the right tool for the job. Languages have different strengths, ecosystems, performance characteristics, type systems, concurrency models, deployment models, and safety guarantees.

Those differences matter, but engineering discussions sometimes give programming-language choice “overvaluation”. In many business systems, choosing between two reasonable languages will matter considerably less than choosing the wrong architecture, the wrong data model, or the wrong operational strategy.

Pick a language appropriate for the scenario. If several languages are appropriate, there is significant value in choosing the one your team understands deeply. Don’t base your decisions on the assumption that LLMs perform better with one PL or another. Always put the team first.

The 5-matters rules are:

  • Fluency matters.
  • Ecosystem maturity matters.
  • Maintainability matters.
  • Debugging matters.
  • Deployment matters.

This is why, today more than ever, with LLMs at our disposal, I encourage engineers not to be religiously loyal to a single PL, but to become as polyglot as possible, learning to use AI assistants as learning tools, not merely as code generators, always double checking official docs.

The real optimization target is the lifetime of the system #

Another mistake is optimizing software exclusively for its current requirements.

Modern software cannot be static, and it must be built to respond to following changes:

  • Users
  • Customers
  • Traffic
  • Business models
  • Regulations
  • Teams
  • Infrastructures

Every point has a weight that can change overtime. So the feature that appears insignificant today can become the core workflow two years later.

Good engineering therefore requires some ability to anticipate change without falling into the opposite trap of designing elaborate abstractions for hypothetical futures that may never arrive, just because teams accepted some codex output, too long to be fully reviewed. That balance is difficult, because:

  • How likely is this requirement to change?
  • Where should we deliberately introduce flexibility?
  • Where should we keep things simple?
  • Which technical debt is acceptable?
  • Which technical debt could become structurally expensive?
  • What should we optimize now?
  • What should we deliberately leave unoptimized until measurements prove that it matters?

This is engineering judgment.

There is no AI that can correctly answer these and many other questions, nor can you simply put an AI agent to work on them and expect a reliable outcome. There is no prompt that makes those questions disappear.

AI-generated code creates a new ownership problem #

There is another issue that I think software organizations are underestimating: codebase ownership.

AI makes producing code extraordinarily cheap. That sounds entirely positive until you remember that producing code and maintaining code are two different economic activities. An engineer can now generate hundreds or thousands of lines of apparently reasonable code extremely quickly.

A team can merge them, tests pass, the feature works, productivity appears to have increased, but something critical may have been lost. And if you want to understand what, ask yourself:

  • Does the team actually understand the general architecture?
  • Do they understand the data structures?
  • Do they understand why a particular abstraction exists?
  • Do they understand the failure behavior?
  • Do they know which assumptions the generated implementation depends on?
  • We can assume they could modify it confidently six months later without asking another AI to explain the code that AI previously generated?

If the answer is no, the team has not eliminated complexity. It has probably introduced others, which become increasingly relevant over time.

Lines of code are cheap. Understanding is expensive. #

This is perhaps the biggest change AI introduces into software economics.

Historically, producing code was relatively expensive. Now it is becoming extremely cheap. While understanding a system, it’s becoming increasingly expensive

That distinction matters: a pull request containing 3K lines of AI-generated code is still a 3K-line addition to the cognitive surface area of your system. And no, a passing-test suite is not the answer, because it gives you a false sense of confidence that everything’s under control because the system -apparently- works as intended.

If you ship it, you own it #

For this reason, I believe strong technical management should maintain a very simple principle: Never ship code you do not own

.

What do I mean by “ownership”? Having a complete understanding of the codebase, having validate every part of it.

AI-generated code should not receive a lower standard of scrutiny because it was generated quickly with some advanced/expensive/frontier/whatever LLM model.

The challenge now is to determine whether (and when) it makes sense to invest time in reviewing such large pull requests, or whether it is better to move more slowly through smaller iterations, where small chunks of application logic are developed through a controlled human–AI exchange.

Software engineering may actually be getting harder #

As a final point, I want to focus on an interesting paradox that seems to have emerged recently:

AI makes programming easier while potentially making software engineering harder.

We can produce more code, faster than ever before. We can create prototypes almost instantly. We can integrate unfamiliar libraries without spending days reading documentation. We can generate tests, APIs, database layers, infrastructure configuration, frontend components, migrations, and entire services at extraordinary speed.

But our capacity to understand systems has not increased at the same rate. And this creates a new bottleneck, which is no longer:

How quickly can we write this?

But:

How much complexity can this organization actually understand and control?

Productivity metrics we used so far, are broken: a team producing five times more code is not necessarily five times more productive. We haven’t been using AI systems in software engineering long enough to have reliable data yet, but it is becoming increasingly clear that the initial productivity gains we saw during the early stages of LLMs adoption, are now coming at a cost in terms of maintaining and evolving moderately complex systems.

From now on, productivity should therefore not be measured by how much software we generate. It should be measured by how effectively we solve problems while keeping the resulting complexity under control.

AI should increase engineering leverage, not replace engineering judgment #

None of this is an argument against AI-assisted development. Quite the opposite. AI is probably the most powerful productivity tool software engineers have received in decades.

But we should use that leverage to spend more time on engineering, not less. We’re allowed to let AI write the repetitive adapter, generate the first version of the test suite, explain an unfamiliar library. It’s extremely useful to let it help investigate an error, propose 3 implementations or refactor mechanical code. It’s a time saver, a booster, an assistant, combined.

Code was never the product #

Software engineering is not merely an academic discipline. We are here to serve our users, so it is a boots-on-the-ground practice, and it needs to remain connected to a real world that is changing faster than ever. It is up to your team to decide how, where, and when AI-generated code fits into your development process.

Programming languages will evolve. Frameworks will disappear. AI models will become dramatically more capable. The cost of generating code will continue approaching zero.

But complexity will remain. It belongs to engineers who can understand the most complexity while introducing the least unnecessary complexity of their own.

AI can help us get there. But it cannot relieve us of our responsibility for what we ship.

── more in #artificial-intelligence 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/software-engineering…] indexed:0 read:13min 2026-08-27 ·