Who will train tomorrow’s senior engineers?
I spent thirteen years at the same company, starting as a junior developer and eventually becoming CTO. Early in that journey, I came up with a caching system that taught me a lesson I still carry around today.
We were building a platform that served multiple websites, and I wanted a convenient way to cache the models used to generate pages. My design had three components: a model containing the data, a loader responsible for constructing it, and a store responsible for caching it. I liked how it separated those concerns and gave developers a consistent programming model.
Unfortunately, it was also painfully slow.
This was before async and await became part of everyday .NET development. The distributed cache meant synchronous network calls, with deserialization and blocked IIS threads adding to the cost. An abstraction that looked elegant in code behaved very differently under load.
We eventually kept the programming model but moved to in-memory stores, with a mechanism to propagate invalidation between instances. That introduced different problems to manage, but avoided the cost of a remote call on every access. I came away much more careful about blocking threads, and much more skeptical of caching as an automatic performance improvement.
I learned those lessons by designing something, discovering that it didn’t work as expected, and investigating it with other engineers. Today I wonder how that experience would have unfolded with a coding agent. It might have spotted the problem before deployment, proposed a better design, and spared us considerable trouble.
That would have been a good result for the product. Would I have learned as much? Possibly, if I’d used the agent to understand thread starvation, investigate alternatives, and challenge the proposed solution rather than simply accept it. The learning opportunity would still have existed, but I would have needed to take it deliberately.
That’s the tension I’ve been thinking about as AI changes the way we build software.
We’re automating the work that taught us
Much of what I consider engineering judgment comes from writing code, debugging failures, making architectural decisions, and finding out that some of those decisions were wrong. It also comes from spending more nights than I’d have liked helping systems recover after unexpected crashes. Those experiences made the consequences of design decisions difficult to ignore.
I don’t think years of manual implementation are the only path to becoming a good engineer, and I have no interest in preserving inefficient practices merely because my generation went through them. I certainly don’t want to return to implementing everything I now delegate to agents.
Still, there’s a difference between getting a problem solved and understanding why a solution works. Implementation often exposes details that are easy to overlook in a design discussion: an abstraction that is unpleasant to use, an unexpected dependency, or an optimization that makes the system worse. As agents take on more of that work, some of those encounters may become less frequent unless we deliberately seek them out.
Other engineering disciplines have long separated designing something from physically building it. A civil engineer doesn’t necessarily lay bricks or pour concrete, but visiting construction sites offers an opportunity to see how designs interact with materials, constraints, and unexpected problems.
I think software engineering may increasingly move in a similar direction, with AI agents handling more of the implementation while humans focus on design, evaluation, and validation. But just as a civil engineer benefits from seeing how a building comes together, a software engineer needs opportunities to understand what happens when their designs become running systems.
Getting the result isn’t the same as developing judgment
I’m hardly immune to the risks of AI-assisted development myself. Working on Xilo, I’ve repeatedly found myself discussing another safeguard, another validation step, or another layer of resilience with an agent. Each suggestion can be reasonable in isolation.
Registration was a good example. We needed to establish which account could use an email address for login and handle situations where different people tried to register with the same address at about the same time. The design gradually acquired its own email-ownership records in the application database, along with transactional claims, conflict handling, and recovery logic to coordinate them. Each addition addressed a plausible problem, but together they were creating a second system for deciding who owned an email address.
Eventually, I stepped back and questioned whether that second system should exist at all. Our identity provider already enforced uniqueness for login identities, so we could leave that responsibility there and remove the custom email-claim protocol. We kept the safeguards that still mattered, including durable registration attempts, checks that an existing identity belonged to the right account, and conditional writes when completing registration. Concurrency and partial failures hadn’t disappeared; we’d simply stopped solving email uniqueness twice.
Because the agent made each extra mechanism so cheap to implement, I’d accepted more complexity than the problem justified before asking that question. I should have challenged the overall design earlier, rather than evaluating each addition on its own.
Knowing when not to build something is part of engineering judgment. But another recent experience reminded me that judging an agent’s work isn’t simply a matter of catching its mistakes.
I was working on a .NET integration that acquired access tokens before sending authenticated gRPC requests. Token acquisition was asynchronous, but the interception mechanism included a synchronous entry point. I had bridged that mismatch by waiting synchronously for the token, which worked in local tests but caused thread starvation and timeouts under concurrent requests.
The agent proposed an approach I hadn’t considered: return a deferred call immediately, acquire the token asynchronously, and start the underlying gRPC operation once the token was available. The design avoided blocking while waiting for a token, though it added some overhead to calls that would otherwise be fast. I could assess that trade-off and recognize the value of the approach, even though it wasn’t one I’d come up with myself.
And yet I didn’t study the technique as deeply as I might have liked. We had a problem to fix, a solution that addressed it, and other work waiting. AI had given me an opportunity to learn something new, but delivery pressure limited how much I took from it.
Those two experiences point in opposite directions. Sometimes my experience helps me recognize that an agent is taking a design too far. Other times the agent contributes an idea I can learn from. Seniority doesn’t mean always knowing better, and accepting a good solution doesn’t automatically mean understanding everything it could teach.
In a previous article about AI and engineering, I argued that architects could use some of the time AI saves on implementation to explore more alternatives and challenge more assumptions. There’s a parallel opportunity for developers, especially juniors: use some of that time to understand the decisions, the implementation, and the failures they might otherwise rush past.
But that won’t happen automatically. Junior engineers don’t always know which gaps in their understanding matter, and an organization focused exclusively on the next delivery can make those gaps easy to ignore. That is where experienced colleagues come in.
Seniors: Join the decision, not just the review
Later in my thirteen-year tenure, I was responsible for interviewing, selecting, and mentoring students during LIA placements, a form of workplace learning in Swedish vocational education. After onboarding, I sometimes gave an intern a relatively complex, low-priority ticket and asked them to think through the solution before writing code.
Our platform served teams in different countries, so a feature requested by one team could contain both a broadly useful capability and details specific to that market. I wanted interns to consider where the boundary belonged. Which parts should live in the shared platform, and which should remain specific to the team requesting them?
I cared about their reasoning, not only their eventual implementation. I should also admit that my younger self sometimes favored elegant, reusable abstractions more than the situation justified. I was still learning to recognize when simplicity mattered more than generality.
I’d use a similar exercise today and encourage an intern to explore options with AI. An agent can propose designs, challenge assumptions, and help identify risks much faster than an intern could investigate everything alone. The interesting part is how the intern makes sense of that material.
Have they considered more than one approach? Do they know which assumptions the designs depend on? Can they explain why one trade-off is acceptable and another isn’t? More importantly, can they recognize when they’re not yet equipped to make the decision on their own?
I’d rather have a junior approach me before implementation and say, “I’ve been exploring a few approaches with AI, but I’m not sure how to evaluate the trade-offs. Can we walk through them together?”
By the time a pull request arrives, much of the design has already been chosen. Reviewing the code is valuable, but joining the discussion earlier gives us a chance to examine the judgment behind those choices. We might even discover that neither of us has the answer and investigate it together.
That’s what seniors can share: not infallible answers, but ways of recognizing uncertainty, challenging assumptions, and deciding what evidence a solution needs.
Of course, making that time available is another matter.
Employers: Make room for the learning you say you value
Mentorship can be difficult even in organizations that sincerely consider it important. Sometimes their incentives make helping colleagues feel like work that takes time away from the real job.
At another point in those thirteen years, when I managed a development team, we used story points to plan capacity. We estimated tickets, assigned a point capacity to each developer, and allocated that capacity among product teams so they could decide what work to request.
Initially, a point was roughly an hour. Over time, though, a particularly competitive developer kept meeting the target we’d set for him, and we kept raising his quota. Eventually, his expected capacity reached eighty points a week. What started as a planning tool had become something much closer to an individual performance target, and management had helped create that pressure.
I eventually removed story points from our process. Instead, we maintained an ordered backlog: product managers could introduce something urgent, but they had to decide what should move down the list to make room for it. We focused on platform availability, robustness, and delivering the highest-priority work with appropriate quality rather than on individual point totals.
I noticed that the developer became more relaxed and more available to help junior colleagues. During a one-to-one, I told him how much I appreciated that change. His response has stayed with me: “Well, now I have the time to do so.”
I can’t attribute every change in behavior to removing story points, but his explanation revealed something about the pressure he’d felt. Helping a colleague had been competing with the target he was expected to reach, and we had kept moving that target.
AI raises a similar question. When an agent helps an engineer finish a task in half the time, what happens to the other half? Does the engineer get an opportunity to explore an alternative design, investigate something unfamiliar, or help a junior understand a problem? Or does the company immediately fill the gap with another ticket?
There’s nothing wrong with expecting delivery. Companies need to create value, and engineers need to contribute to that. But if every improvement in productivity becomes a higher throughput expectation, learning and mentoring will remain things people are supposed to fit around their actual work.
I don’t think the answer is to impose a fixed quota of learning hours or stop juniors from taking new tickets. That would risk turning learning into another number to optimize. Managers can instead make it normal to ask for help, recognize the value of helping others, and take an interest in whether someone’s judgment is developing alongside their output.
There’s also a problem that individual companies cannot solve merely by optimizing their own hiring decisions.
Developing junior engineers takes time and money. Some will eventually leave, carrying their experience to another company. An employer might therefore decide that recruiting someone already experienced is easier than developing that experience internally. From one company’s perspective, that decision may be entirely rational.
But if every employer makes it, who will be left to hire? Everyone benefits from an industry with experienced engineers, while each company has an incentive to let someone else pay for training them. If too many companies act on that incentive, the pool they all depend on may stop replenishing itself.
AI could sharpen the problem. If experienced engineers can deliver more with agents, the immediate business case for hiring juniors may become weaker even while the longer-term need for experienced people remains. This isn’t an argument for preserving historical hiring numbers, or for pretending every company will recover its training investment. It’s about recognizing a shared dependency that individual short-term decisions may fail to protect.
I saw the direct benefits of developing people internally during my own career. Engineers who grew with the company learned the codebase, certainly, but they also accumulated knowledge of past decisions, business constraints, and the organization itself. That context made them valuable in ways that couldn’t be measured by the number of tickets they closed.
We don’t need to solve the industry’s training model in this article. We do need to recognize that a profession cannot indefinitely rely on expertise that nobody is helping to develop.
Juniors: Take responsibility for becoming engineers
So far, I’ve focused on what experienced engineers and their employers can do to develop the next generation. But there’s another side to that responsibility. Even when a company invests in mentoring and seniors make themselves available, none of it guarantees that a junior will actually grow.
Learning requires the junior to participate actively, to be curious, to question what they don’t understand, and sometimes to resist the temptation to simply move on to the next task.
I wrote previously that juniors should optimize for growth, not just productivity. AI makes that distinction even more important.
A capable agent can help someone early in their career deliver work that would previously have taken much longer. That’s valuable, but getting good at producing results isn’t the same as developing the ability to reason about increasingly difficult problems.
I like to think of AI as the car you drive, not the train you ride. Both get you places faster, but only one keeps you behind the wheel. Using an agent doesn’t mean giving up control of the journey. You can let it handle much of the implementation while still choosing a direction, questioning the route, and noticing when something doesn’t make sense.
That last part matters even when the code works. If an agent has made decisions you don’t understand, it’s tempting to accept the result and move on, especially when the tests pass and the next task is waiting. But understanding why the code takes that shape is part of learning to make those decisions yourself. You can investigate with the agent, trace the behavior, or ask a more experienced colleague to work through it with you.
This is also why I wouldn’t judge a junior’s progress by the finished output alone. Imagine two interns given the same problem. One returns a polished implementation with tests and documentation but cannot explain several design decisions because an agent made them. The other has explored different approaches, understands the trade-offs, and has implemented the main flow, but hasn’t yet covered failure cases, written tests, or documented the solution.
Neither result is enough on its own. I’d ask the first intern to investigate and explain the decisions behind the code, and the second to finish and polish the solution. What I’d be interested in is how they respond to that feedback, because becoming an engineer requires both the ability to reason about a problem and the ability to deliver something useful.
Seniors can help develop that reasoning, and employers can make room for it. The junior still has to take the opportunity seriously. Learning shouldn’t become an excuse for unfinished work, but neither should a finished task be treated as proof that the engineer has learned from it.
We may need fewer engineers. We still need to develop them.
I don’t assume software engineering will always employ the same number of people doing the same work it does today. AI may reduce the need for human implementation, and perhaps even the number of senior engineers organizations require. It’s also possible that product owners will eventually direct agents capable of handling almost the entire technical engineering process.
I don’t know if or when that future will arrive. What concerns me is the period between now and then.
Today’s experienced engineers won’t remain in hands-on technical roles forever. Some will retire, while others will move into management or different kinds of work. Meanwhile, AI is taking on more of the implementation and debugging work through which many of us learned. If those opportunities diminish before we establish other ways to develop judgment, we could end up with fewer people capable of overseeing the systems we’re building.
That possibility doesn’t justify preserving every traditional junior role or requiring years of manual coding as a rite of passage. It does mean we cannot assume that the experience people once gained through everyday implementation will somehow develop on its own. AI can help engineers learn faster and explore problems they might never have encountered otherwise, but only if they take the time to understand what they’re doing.
Making that possible is a shared responsibility. Experienced engineers need to explain how they reason, not only hand over solutions. Employers need to make room for those conversations, and the industry needs to recognize the consequences if everyone expects someone else to develop its future talent. Juniors, in turn, have to make use of those opportunities. None of this requires us to preserve the profession exactly as it is today.
Recap
In this post, we looked at how AI is changing not just the work software engineers do, but also the experiences through which they learn to do it well. We considered what seniors, employers, and juniors can each do to make sure engineering judgment continues to develop as more implementation moves to AI agents.
We may not need as many engineers in the future. We still need to figure out how to develop the ones we do need.
This article was produced using an AI-assisted editorial process and was reviewed with AI assistance. Read about my editorial process.
Support this blog
If you liked this article, consider supporting this blog by buying me a pizza!