Always human authored · no generative AI
AI is for Seniors, Only
Why AI Coding Assistants are Foot-guns for Newer Devs

Let me explain myself
Okay, that statement is a little hyperbolic, but I do think there’s enough truth to it to justify itself. I’m not trying to gatekeep, I promise. I’m moreso trying to think of the longterm ramifications of AI generated code.
Here are the reasons I think junior engineers shouldn’t use AI in no particular order:
- AI be wrong a lot
- LLMs can’t think
- I’m going to
dieretire one day
Allow me to go through these one-by-one and explain why that leads me to believe that juniors should stay away from AI, at least in the early days of their careers.
AI be wrong a lot
At my current place of work, we have some junior engineers, and they’re great. They’re smart and talented, and they have a great future ahead of them. However, they’re inexperienced, and they don’t know what they don’t know.
A great example of this is when one of our more junior engineers took on the project of standardizing our error reporting across all of our many services. They came up with a really solid plan, and a completely reasonable implementation… in Python. That’s fine, since most of our services are written in Python, but one of our core services–one that I happen to be the primary maintainer on–is written in TypeScript.
But no bigs right? The Python implementation defined a base error class from which the more specific
errors inherited from. That’s a completely reasonable approach, and theoretically it should
translate 1:1 to TypeScript. But that service wasn’t just written in TypeScript, it was written in
Effect.ts where bespoke errors are more than just extensions of the Error class.
So when they told the AI to implement their schema in TypeScript, it generated something to the effect of:
export abstract class AppError extends Error {
abstract readonly code: string;
abstract readonly httpStatus: number;
abstract readonly category: ErrorCategory;
abstract readonly retryable: boolean;
abstract readonly userMessage: string;
// etc etc some special methods and whatnot
}
export abstract class UnauthorizedError extends AppError {
readonly httpStatus = 401;
readonly category = ErrorCategory.AUTH;
readonly retryable = false;
}
export class SessionExpiredError extends UnauthorizedError {
readonly code = 'auth.session_expired';
readonly userMessage = 'Your session has expired';
}
But that’s not how it’s done in Effect.ts. No big deal. When I reviewed the code, I left a comment with a sketch of how it should look, and figured that would be the end of it. But the AI fought them on it! It insisted that the approach it took was idiomatic, and that my comment should be disregarded. Eventually, I jumped in and had to son the AI until it relented and agreed that my approach was correct.
For the record, it should have looked something like this:
import { ErrorCategory } from './categories.js';
import { AppErrorMixin } from './taggedError.js';
// AppErrorMixin creates a new Data.TaggedError which is yieldable and compatible
// with Effect.ts observability and comes built in with serialization and deserialization methods.
// It's more verbose, but it pays for itself in its flexibility and power.
export class NotAuthenticatedError extends AppErrorMixin(
Schema.TaggedError<NotAuthenticatedError>()('NotAuthenticatedError', {}),
{
code: 'auth.not_authenticated',
httpStatus: 401,
category: ErrorCategory.AUTH,
retryable: false,
userMessage: 'Not authenticated'
}
) {
constructor() {
super({});
}
}
The problem is that the junior engineer did nothing wrong. How would anyone expect them to know this level of nuance? The problem is that the AI was not only mistaken, but also insistent that it was correct. The only reason I was able to call it out is because of my experience.
This may seem like an inconsequential example, but imagine how this exact issue could compound if the AI continued to mix-and-match different paradigms into the same codebase. Eventually the code would become incomprehensible even to an LLM, and the company would have to spend precious time and money refactoring, or worse, rewriting the service from scratch.
The only thing that saved us from that was my experience as a senior. Juniors being mistaken about
something like this is nothing new, but the thing is that AIs write a lot of code. Where once
inexperienced developers would be wielding a hatchet–limiting how much damage they could
do in one swing–they now have a chainsaw. The blast radius has grown substantially. Furthermore,
anyone that tries to suggest that tech-debt is a thing of the past because of AI is someone who
should never be allowed to use AI on a production codebase. That’s a person who should only ever
be deploying to localhost.
LLMs can’t think
When an LLM tells you that it’s “thinking”, that’s just marketing. All it’s doing is just recursively prompting itself. It cannot think in a way that is categorical similar to the way humans think. I will admit that LLMs do exhibit emergent behaviors that do look a lot like reasoning, by virtue of it simulating human chains of reasonsing, but it’s still just auto-complete under the hood.
The last example serves here too. Why was it so hard to convince the AI that since we’re using Effect.ts, we have to be consistent in the idioms we use for our error handling? Because it’s been trained on millions of examples of more traditional class inheritance and without the actual ability to think, it couldn’t see past this.
The problem grows when we start thinking about higher-level concepts like architecture or API
design. The AI is happy to write a couple thousand line switch statement, complete with nested
if statements, but will never reach for a reducer or state machine. It’ll gladly write a component
with fifty props, but won’t think to use Composite Components or Context.
The fact is that as it currently stands, humans are still smarter than AI. Scratch that. Humans aren’t just smarter; we can actually think. We’re not even in the same league. The “I” in AI is, in my opinion, a misnomer. It’s not genuine intelligence, only the appearance of intelligence. One day, we as a species need to understand that sounding intelligent and being intelligent are two different things. Now is as good a time as any to collectively wrap our heads around that concept.
I’m going to retire one day (hopefully)
I’m not always going to be around to catch potentially catastrophic mistakes before they’re merged, and Code Rabbit will never replace the hard-earned wisdom that comes from decades of hand-crafting production code. So where does that leave the new generation of engineers if all they have is the superficial and unverified “expertise” of large language models? I would love to be wrong, but as I see it, the trajectory we’re currently on leads directly into a brick wall. It really can’t be understated how big of a problem it will be when AI slop takes over core systems and there are no experts left to untangle the spaghetti.
So, I stated in the beginning that juniors shouldn’t use AI. For that to be a realistic prohibition, tech companies to take on the responsibility of cultivating their juniors into seniors. We can’t just be fostering a generation of prompters. We need engineers. The only way to do that is to accept that the productivity loss that comes from juniors hand-crafting their code. It’s never popular in our current economic structure to eschew short-term gains for the long-term, but if we want to continue to build cutting-edge technology, we’re going to need human engineers for a long time. To have those engineers in the future, we need to teach them now, and the best way to learn is getting your hands dirty, and learn the hard way why “it works” is not good enough.