The folly we keep choosing
The dominant industry response to the AI coding crisis is to hire fewer juniors. Some companies are saying it out loud now. Most are doing it quietly. The reasoning, when anyone bothers to spell it out, is that juniors aren’t keeping up – they’re shipping bad code, the seniors are drowning in review, and the tools have made experienced developers leveraged enough that maybe we just don’t need as many entry-level people anymore.
This is folly. It is also, more uncomfortably, a self-inflicted training failure that we keep blaming on the juniors instead of on ourselves. We are not failing to find capable juniors. We are failing to teach them at the level the work now happens. The agents took over the basics. We kept teaching the basics. Then we acted surprised when the juniors couldn’t do the higher-level work nobody had taught them.
Quick disclosure before we go further. I run a training company called Groxio that has been building programs around exactly this kind of work for the last few years. You don’t need to train with us. You need someone to train with you. There are real options out there, and I’d rather you train with anyone than train with no one.
Here is what we have to teach, and what we have to teach is not mysterious. Almost all of it is work that senior engineers used to do invisibly – sizing the right chunk, telling the story of a change, recognizing what’s worth keeping, reading a system at a higher level than its lines of code. It used to transfer to juniors slowly, by osmosis, in the gap that article 2 mourned. The gap is gone. The transfer doesn’t happen automatically anymore. So we have to teach explicitly what we used to absorb implicitly. The good news is that this is trainable – these are skills, not innate talents, and they can be taught in weeks rather than years if we decide to teach them.
Here are four places to start.
The pull request: size, scope, and storytelling
A pull request used to be a conversation. The author proposed a change. The reviewer pushed back. Both sides got smarter. The PR shipped or it didn’t, but everyone learned.
PRs have become deliveries. The author hands over a thousand lines of plausibly-shaped code. The reviewer is supposed to bless it. Nobody learns. This is not the juniors’ fault – it’s the rhythm the tools encourage – but it is the juniors’ problem to solve, because nobody else is going to solve it for them.
The combined skill is choosing a chunk you can hold in your head, scoping it to one idea, and shaping the PR so a reviewer can evaluate it without guessing. Concretely: target PRs around two hundred lines of meaningful change, not two thousand. Open with a description that says what changed, why it changed, what was considered and rejected, and where to look first. Write commit messages that do narrative work. Split aggressively when a PR starts to outgrow itself.
This used to be learned by watching seniors write PRs over years. We don’t have years. So we teach it directly. A workshop with three or four example PRs – one good, one bloated, one that mixes unrelated changes, one that does narrative work – gets a junior most of the way there in an afternoon.
The abstraction level moved up
This is the part of the article I most want you to take seriously, because it pushes back on something the industry has assumed for a long time.
We used to train programmers from the bottom up. First the language – syntax, control flow, data structures. Then the building blocks – modules, functions, the small patterns. Then, eventually, the architectural concepts – inversion of control, error propagation, concurrency models – because those concepts are built out of the lower-level primitives, and you couldn’t wield them with judgment until you understood what they were made of. Every programming book I grew up on was organized this way. Chapter one was variables. Chapter twelve was design patterns. You climbed the abstraction stack one rung at a time, and that was how you became senior.
That training model assumed you’d be writing the primitives yourself for years before you got to think about architecture. The agents took that assumption away. They write the primitives now, mostly. The work that’s left for the human – the work juniors are doing whether we trained them for it or not – has moved up the stack. They’re operating at the architectural level on day one, evaluating whether the agent’s output fits, deciding where the IoC seams should be, judging whether the error model is consistent. The work moved up. The training didn’t.
We still need juniors who understand the language. We just can’t afford to make that the only thing we teach them in their first six months, because the work they have to do operates at a higher level than the work we’re teaching. The bar for what a junior should be expected to see has risen – not because juniors have changed, but because the level the work happens at has changed. The tools have moved up too. So can our training.
Three categories of question, to start with.
Where is inversion of control happening? Every codebase has seams where the system, not your code, is in charge of when things run. In Elixir those seams live in LiveView, in GenServer callbacks, in behaviours. In Java they live in IoC containers and frameworks like Spring. In JavaScript they live in React’s component lifecycle, in event handlers, in the browser itself. The junior should walk into a codebase on day one and find these seams without being told. Who is calling this function? used to be a senior question. We can teach juniors to ask it from the start.
How do errors propagate? What’s the error model – exceptions, tagged tuples, Result types, callback patterns, something custom? When something goes wrong, where does the failure travel? What catches it? What doesn’t? What’s the team’s convention for handling expected failure versus genuinely unexpected failure? Juniors don’t need to design error systems. They do need to read them, and they can be taught to read them faster than anyone currently believes.
What are the concurrency implications? What’s running concurrently, what’s serialized, where can things race, where can they deadlock? In Elixir that means understanding processes and message passing. In other systems it means threads, async runtimes, event loops, transactions. The junior who can ask what could be running at the same time as this? is doing senior-level reasoning at junior-level depth, and that is exactly the level we need them to operate at.
These are not language-specific skills. They’re categories of question – three lenses for reading any codebase. The vocabulary changes across stacks. The questions don’t. The agents can’t ask these questions for us – that was article 2’s whole point about the IoC layer. So the juniors have to. We can teach them. We mostly haven’t tried.
🎯 Join Groxio's Newsletter
Weekly lessons on Elixir, system design, and AI-assisted development — plus stories from our training and mentoring sessions.
We respect your privacy. No spam, unsubscribe anytime.
The throw-it-away discipline
Article 2 handed every reader a permission slip: it’s okay to throw it away. Article 3 turns that permission into a discipline.
The skill is recognizing what’s worth keeping and what isn’t, fast – and the recognition is concrete enough to teach. Every codebase has signals. Readable, learnable, codebase-specific signals that distinguish code that fits from code that doesn’t.
In Elixir, look down the left margin. Are you seeing pipes in the functional core module, and with statements in the context layer? Or are you seeing tagged tuples everywhere, and a mess of nested case statements scattered across files that should be doing different jobs? The first shape – pipes where the data flows cleanly, with where the boundary handles failure – is idiomatic. The second is a sign that whoever wrote it (human or agent) didn’t know which module was supposed to be doing what.
In Ruby and other object-oriented languages, watch for message chains that drop through nil. Is the failure mode handled by the codebase’s convention, or is the chain hoping nothing goes wrong? Are exceptions being raised at the boundary or swallowed in the middle? The shape of the chain tells you whether the author understood the codebase’s failure model.
In any codebase, the shape of a function relative to the rest of the codebase is itself a signal. A function that’s twice as long as everything around it. A module that doesn’t follow the team’s naming conventions. A test that asserts on implementation details when the rest of the suite asserts on behavior. These are signs that something arrived without the design context the rest of the codebase was built with.
The training is concrete. Sit a junior down with a pile of recent PRs and walk through, one by one, the signals that tell you this fits or this doesn’t. Do it on the team’s actual codebase, with the team’s actual conventions. A few sessions of this and the junior can do what they couldn’t do the week before – close a PR they wrote, look at it with fresh eyes, and recognize that something is off without being able to fully articulate why. That gut-level recognition is taste. We have always treated taste as something you developed over years. It turns out you can train its first layer in a couple of weeks.
The junior who has this discipline doesn’t negotiate with bad AI-generated code. They throw it away and ask for a smaller version.
Prompt layering
This is where the article’s other three skills come together, and it’s also where I want to dispel a common framing.
People talk about prompt engineering as if it’s a novel discipline that nobody has experience with. That’s not quite right. The principles that make a prompt chain maintainable are the same principles that make code maintainable. Compose small units that do one thing well. Build clean interfaces between them. Layer the abstractions so each layer has a single responsibility. Separate concerns. Don’t let the boundaries blur.
A prompt chain is a program written in English. The skills, the scripts, the hooks, the prompts themselves – they compose the same way functions and modules compose. The interfaces between them matter. The layering matters. The separation of concerns matters. A senior engineer who has spent twenty years designing clean modules already knows most of what they need to know to design a clean prompt chain. They just haven’t named the transfer yet.
This is also why the other three skills in this article are prerequisites for this one. You can’t size a prompt unit without knowing how to size a chunk of work. You can’t write prompts that produce code that fits a codebase without reading the codebase at the architectural level. You can’t recognize when a prompt has produced something worth keeping without the throw-it-away discipline. Prompt layering is where those skills get applied to the new medium.
The medium is fuzzier than code. Natural language is ambiguous in ways a typed function signature isn’t. The agent sometimes interprets a prompt differently than you intended. Compositions sometimes leak across boundaries you thought were clean. So the analogy is strong but not exact, and the discipline takes practice – but it’s a familiar discipline practiced on slightly unfamiliar material, not an alien one.
Train it that way. Pair a junior with a senior on a real task. Have them build the prompt chain together, talking through the same composition decisions they’d make in code. The senior will be surprised how much of their existing judgment transfers. The junior will be surprised how much faster they can learn this when it’s framed as something they were already trying to learn.
What you can do next week
The juniors aren’t the problem. They never were. The problem was that we stopped teaching them and started blaming them for not learning.
Pick a practice from this article. Find a junior on your team. Spend a Friday afternoon. That’s literally what it takes to get started. A workshop with example PRs. A walk through the codebase asking the three design questions. A pile of recent diffs and a senior teaching what to throw away. A pairing session on prompt layering. Bootcamp intensives, ongoing weekly mentorship, embedded coaching, internal champions – the shape can be whatever fits your team. The point is that training is a real activity that you can start now, not a vague aspiration.
This is the cheapest, fastest, most reversible piece of the whole crisis. The structural piece – what the system around your team has to change – is harder, and it comes next.
But the most countercultural claim in this whole article isn’t any of the practices. It’s the underlying bet: we can have juniors who matter again. The industry has spent the last two years organizing itself around the assumption that the answer is fewer juniors. That assumption is wrong. We can have more juniors, doing meaningful work, contributing to the codebase from their first week – if we train them.
Next time, the system around them.
🛠 Train Juniors at the Level the Work Now Happens
This post is from Bruce Tate's series on what the AI coding crisis is doing to engineering teams — and what it would take to train through it instead of around it. Groxio runs private training and ongoing advisory for engineering teams using AI with Elixir, Phoenix, OTP, LiveView, Ecto, Ash, and Postgres. We start with a diagnostic conversation about where your review queue, your seniors, and your codebase actually are.
— Bruce