The interesting thing about AI-assisted engineering in 2026 is not that it works. That argument is over. The interesting thing is which parts of the job got easier, which got harder, and which quietly moved somewhere else while everyone was looking at the demos.

Here is what I actually see across the codebases we work in — ours and our clients’.

The bottleneck moved, it didn’t disappear

Writing code is no longer the constraint on how fast a team ships. For a large class of work — a CRUD endpoint, a form, a migration, a test suite for a pure function, a refactor with a clear shape — the code appears roughly as fast as you can specify it.

The constraint is now verification. Somebody has to decide whether the code is right, whether it belongs in the codebase, and whether it will still be understandable in eighteen months. That was always part of the job, but it used to be a fraction of the effort of writing. Now it is most of it.

The teams that got faster are the ones that recognised this and moved people and time into review, testing, and design. The teams that did not are producing more code than they can absorb, and their velocity graphs look great for about two quarters.

Code got longer

This is the change nobody puts in the marketing material, and it is the one with the longest tail.

AI-generated code is verbose. Consistently. The same function a human would write in twelve lines arrives as thirty-five: defensive null checks on values that cannot be null, a try/catch around an operation that cannot throw, three comments restating what the next line does, an options object with parameters nobody passes, and a helper extracted from a single call site.

None of it is wrong, exactly. That is why it survives review. It is just more code than the problem needed, and more code is not free:

  • Every line is a line someone reads later, when they are debugging something else at 2am.
  • Preemptive abstraction is worse than no abstraction, because the wrong seam is harder to remove than a straight line of code.
  • Defensive checks against impossible states obscure the checks that guard real ones. When everything is guarded, guards stop carrying information.
  • Comments that restate code go stale and then actively mislead.

The cause is structural rather than a bug to be fixed. A model generating code is optimising for output that looks complete and defensible. It does not experience the cost of a line — it will never be the one reading this file in a year. Terseness is a judgement about future cost, and judgement about future cost is exactly what a generator without stakes cannot supply.

The mitigation is mundane: treat length as a review signal. When a diff is three times the size the problem warranted, that is worth a comment, the same as a naming problem. Codebases where nobody pushes back on this get noticeably harder to work in over about a year — and the decay is gradual enough that no single pull request is ever the culprit.

Machines read pull requests first

Automated review is now the first pass almost everywhere, and it is genuinely good at a specific and narrow set of things: unhandled error paths, resource leaks, off-by-one errors, missing null checks that actually matter, inconsistent error handling across sibling functions, obvious injection risks, and tests that do not test what their name claims.

That is real value. Those categories used to consume a meaningful share of human review attention, and reviewers were bad at them — they are exactly the kind of mechanical scanning humans do poorly and machines do tirelessly.

What automated review is bad at is everything contextual:

  • Should this exist? No reviewer without your roadmap can tell you the feature is a mistake.
  • Does this duplicate what we already have? Models reliably reimplement utilities that exist three directories away, and the reviewer that missed it is often the same model.
  • Is this the right abstraction for where the code is going? Requires knowing where the code is going.
  • Will the on-call engineer understand this at 3am? Requires knowing the on-call engineer.

So human review changed shape. Less line-by-line hunting for missing error handling, more asking whether the change makes sense at all. In my experience that is a more interesting job and a more tiring one — mechanical review had a rhythm to it, and design review does not.

The trap is letting the machine pass become a substitute for the human one. A clean automated review means the code has no obvious mechanical defects. It says nothing about whether the code should be merged. Teams that let a green check stand in for judgement ship more subtly wrong software, not less.

The new failure modes are quieter

Old bugs announced themselves. The code did not compile, the test failed, the null pointer threw. AI-era bugs are more polite.

Plausible and wrong. Code that reads perfectly, uses an API that does not exist, or uses one that does exist with semantics slightly different from what was assumed. It fails at runtime, in a branch your tests did not cover.

Tautological tests. A test generated from an implementation asserts what the implementation does, not what it should do. It passes forever, including through the bug. This is worse than no test, because it manufactures confidence.

Silent duplication. A model that cannot see your whole codebase writes a new date formatter, a new retry wrapper, a new validation helper. Six months later you have four of each, subtly different, and a bug fixed in one of them.

Comprehension debt. The one I worry about most. Code that works, that nobody on the team has actually read closely. It is fine until it breaks, and then the debugging starts from zero because there is no mental model to fall back on.

That last one deserves its own attention. We have always had code nobody understands — it usually arrived through turnover, and took years. Now it can arrive on a Tuesday. The countermeasure is not sophisticated: whoever puts the code up owns it, and owning it means being able to explain it. If you cannot explain what you are merging, you are not ready to merge it, regardless of who or what wrote it.

What actually works

The practices that separate teams doing this well from teams drowning in their own output are not exotic.

Small diffs. The efficiency gain tempts you toward large changes, because large changes are now cheap to produce. They are not cheap to review, and review is the bottleneck. A 2,000-line pull request gets rubber-stamped by humans and machines alike.

Specify before generating. The quality of what you get back tracks the quality of what you asked for, more strongly than it tracks the model. Ten minutes deciding what the interface should be beats an hour correcting an interface you did not choose.

Tests as the specification, written first. A human-written test defines the contract, and generated implementations get measured against it. This inverts the tautological test problem cleanly.

Types and lints as a forcing function. Static typing catches an outsized share of plausible-but-wrong code before a human ever sees it. Strict compiler settings and a strict linter went from “nice discipline” to “load-bearing infrastructure.”

Architectural decisions stay human. Data models, service boundaries, what runs where, what depends on what — these are the choices that are expensive to reverse, and they depend on constraints that live outside the codebase. Getting them right still looks like designing an abstraction so a decision stays reversible, and no amount of generation speed makes a wrong boundary cheaper to move later.

What didn’t change

Requirements are still the hard part. The majority of failed software projects I have seen failed because someone built the wrong thing carefully, and generating the wrong thing faster does not help.

Distributed systems are still difficult, because their failures are emergent rather than local, and a model reading one file cannot reason about what happens when three services partition simultaneously.

Legacy systems still resist. Their complexity is undocumented context — the reason for a strange condition added in 2014, the customer who depends on the bug. That knowledge is not in the code, so it is not available to anything reading the code.

And judgement is still the job. Which of these three approaches fits the team, the budget, and the next two years. When to ship the imperfect version. What to leave out. Those decisions carry more weight now, not less, because the cost of executing on a bad decision fell and the volume of decisions rose. The same lesson keeps surfacing everywhere in this work — a framework choice is rarely the decision that determines how a project goes, and neither is a tooling choice.

The honest summary

Engineering in 2026 is genuinely faster, genuinely more productive, and genuinely more demanding of good judgement than it was five years ago. The floor came up — mediocre code is now easy to produce and easy to screen. The ceiling did not move much, because the ceiling was never about typing speed.

If the practical question is how to build with AI in production without accumulating the debt described above, or how to get a team’s review and testing practice to keep pace with its new output, that is the work our advisory engagements tend to focus on now. Tell us where it’s hurting — the diagnosis is usually quicker than people expect.