LLMs and the Changing Role of Software Engineers
The debate about what large language models mean for software engineering tends to collapse into two camps almost immediately: either the job is being automated away, or the tools are overhyped and nothing fundamental is changing. I don’t find either framing useful, mostly because both treat “software engineering” as one undifferentiated skill, when it’s obviously several different skills bundled together that have never scaled at the same rate.
What I’ve actually noticed, spending real time with these tools rather than arguing about them in the abstract, is that they’re extremely good at a specific subset of the job and not particularly good at another subset, and the two subsets were never equally valuable to begin with — they just used to take a similar amount of time, which made them feel more equivalent than they actually were.
The part that’s shrinking
The mechanical translation from a clear intention to working code — I know exactly what this function needs to do, now I need to actually type the implementation — has gotten dramatically faster. Boilerplate, glue code, straightforward CRUD operations, test scaffolding, the kind of code where the hard part was never the thinking but the typing: a model handles a meaningful fraction of that now, often correctly on the first attempt, for a large class of common patterns.
This is real, and I don’t think it’s overhyped. But it’s worth being precise about what’s actually shrinking, because it’s not “programming.” It’s the specific subset of programming where the specification was already unambiguous and the implementation was mostly a matter of knowing the right syntax and library calls. That subset was never where the hard part of the job lived for anyone who’d been doing it a while — it was just the part that consumed a disproportionate amount of clock time, because typing takes time even when thinking doesn’t.
The part that isn’t shrinking
What hasn’t gotten meaningfully easier is figuring out what to build, translating a genuinely ambiguous problem into a well-specified one, and knowing whether a given solution is actually correct, maintainable, and appropriate for the system it’s going into. If anything, this part has gotten more important, because it’s now the bottleneck in a way it wasn’t before — when typing was the bottleneck too, mediocre specification could hide behind the general slowness of everything. Now that the mechanical part is fast, the quality of your specification shows up immediately in the quality of what comes back, with much less slack to compensate for it later.
This shows up very concretely in how differently a vague prompt and a precise one perform. “Add caching to this function” gets you something that runs. It might cache the wrong thing, invalidate incorrectly, or introduce a subtle correctness bug under concurrent access, and a plausible-looking implementation won’t announce which of those it did. “Add an LRU cache with a five-minute TTL, keyed on the normalized request parameters, that must remain correct under concurrent access from the async workers in this file” gets you something much closer to right, immediately. The gap between those two prompts isn’t a prompting trick — it’s the actual engineering judgment the task required, made explicit instead of left implicit. The model didn’t remove the need for that judgment. It just made the cost of skipping it visible faster than it used to be, because a bad specification returns a bad implementation in seconds instead of after an hour of writing it yourself.
Review is now the bottleneck, and it’s a harder skill than it looks
The most significant shift I’ve noticed isn’t in writing code, it’s in reading it. When you write something yourself, understanding accumulates as a side effect of the writing — by the time it’s done, you’ve built a mental model of it because building that model was how you wrote it in the first place. When a model produces a large chunk of code in seconds, you don’t get that side effect. You have to build the same understanding after the fact, deliberately, and that’s a meaningfully different and in some ways harder cognitive task than writing it would have been.
This has made me take code review much more seriously as a discipline in its own right, rather than treating it as the easier, faster cousin of writing code. Plausible-looking generated code is a specific hazard here — it tends to fail in the direction of “obviously reasonable at a glance,” which is exactly the property that makes shallow review dangerous. A human’s bad code often looks bad, because it carries the visible signature of confusion or rushing. A model’s bad code frequently looks identical to its good code, stylistically confident either way, which means the reviewer can’t rely on the usual “this looks off” instinct nearly as much and has to actually verify claims rather than pattern-match on style.
What I think this means for how you build the underlying skill
There’s a real concern, and I don’t think it’s a paranoid one, that engineers who lean on these tools heavily from early in their careers might not build the same depth of the underlying skill that engineers who spent years writing everything by hand accumulated somewhat automatically, as a side effect of doing the work the slow way. I don’t think this concern is fully resolved yet, and I’d be suspicious of anyone who claims it obviously is.
But I’d frame the risk narrowly rather than broadly. The risk isn’t “using these tools prevents learning.” It’s “using these tools as a substitute for understanding, rather than as an accelerant to producing something you still fully understand, prevents learning.” Those produce very different long-term outcomes and look identical in the short term, which is precisely what makes the distinction easy to blur. The habits from the studying-hard-material side of this still apply here directly: if you accept generated code without reconstructing why it’s correct, you’re in the position of someone who read a solution manual without doing the exercise. It feels like progress. It builds nothing durable.
The engineers I’d bet on in five years aren’t the ones refusing to use these tools on principle, and they aren’t the ones accepting everything the tools produce without scrutiny either. They’re the ones using the speed to explore more options and iterate faster, while still doing the work of actually understanding what got built — treating the model’s output the way you’d treat a draft from a very fast, occasionally overconfident collaborator, not the way you’d treat a finished, trusted answer.
The actual shift in the job
If I had to compress this into one claim, it’s that the value of software engineering is consolidating around the parts that were always the hardest parts, and shedding some of the parts that were merely time-consuming. Specification, judgment, architectural taste, the ability to tell whether something is actually correct rather than merely plausible — that’s not new value the tools created. It’s value that was always there, previously diluted across a job that also required a lot of mechanical typing to express it. Strip the typing bottleneck away, and what’s left is a much more concentrated version of the skill that was always the real one.
That’s a more unsettling framing for some people than either “the job is being automated” or “nothing is changing,” because it doesn’t offer an easy verdict. It suggests the job is getting harder in the specific dimension that was already hard, at the same time it’s getting easier in the dimension that was mostly just tedious. Both of those things being true at once is a less satisfying story than either extreme, but it’s the one that matches what I’ve actually observed.