The Code Looked Fine. That Was the Problem.
AI can write code fast, but it has no idea whether that code survives contact with real users. Four bugs it will happily hand you, and why understanding the system underneath still matters more than ever.
A few weeks back, a friend of mine (frontend engineer, ships fast, leans hard on AI tooling) built a product page that pulled live inventory counts from an API. It worked perfectly in every manual test he ran. Then it hit prod and started randomly showing the wrong stock number. Sometimes for a second, sometimes until the next click. No crash. No error in the console. Just numbers that were occasionally, quietly wrong.
He'd built the fetch logic with an AI assistant. Clean useEffect, clean fetch call, clean state update. He pasted the bug back into the same assistant. It suggested a loading state. Didn't help. It suggested error handling. Also didn't help, because there was no error to handle. Two hours in, a teammate glanced at it for maybe 30 seconds and said: "you're not cancelling the old request when a new one fires, so whichever response lands last wins, even if it's the stale one."
Classic race condition between two async calls resolving out of order. The kind of bug you spot in seconds once you actually understand how the JavaScript event loop schedules things, and one that AI will write for you all day long without ever flagging it, because on the page, the code looks completely fine.
That's basically the whole essay. Everything below is me unpacking why this keeps happening to smart people, and why the current "you don't need CS fundamentals anymore, just learn to prompt" take has it backwards.
The discourse, for anyone who's missed it
You've seen the posts. Bootcamps rebranding "Full Stack in 12 Weeks" into "AI Native Engineer in 6." Threads insisting that data structures, algorithms, and "how the browser actually works" are hazing rituals from a bygone era, irrelevant now that a model can spit out a working component faster than you can type its props. The vibe: understanding how things work is a legacy cost. Knowing what to ask for is the new skill.
There's a real insight buried in there. Prompting well is a skill, and it's not nothing. But the conclusion people are drawing from it is wrong in a specific, checkable way: it confuses producing code with understanding a system. AI collapsed the first one. It did nothing to the second. And the second one was always where the actual engineering lived.
What AI actually gives you
Here's the honest version of what changed. AI is extraordinary at generating plausible, syntactically correct, locally reasonable code, fast. That's a real unlock. It is not extraordinary, and it isn't even reliably okay, at knowing whether that code holds up under real user behavior, real network conditions, real re-render cycles, or six months of other people touching it.
Think of it as two layers. The top layer is what AI shows you: it compiles, it renders, it passes the tests you thought to write, it ships. The bottom layer is everything that makes an app actually survive contact with real users: how the JS engine handles memory and closures, how the event loop schedules async work, how the browser paints and reflows, how your component tree actually re-renders under the hood. AI operates almost entirely on the surface. It doesn't run your app in a real browser with a flaky connection and an impatient user mashing the search bar. You do.
Nobody ships a broken UI because AI can't write a component. They ship one because the "clean" solution re-renders the whole tree on every keystroke, or the two fetches resolve out of order, or the list rendering is fine with 20 items and grinds to a halt at 2,000. AI will confidently hand you all three. It won't tell you which one it just handed you. That part's still on you.
Four bugs AI will happily write for you
None of these are exotic. They're a normal Tuesday for anyone shipping code with AI assistance.
The brute force trap. Ask an AI model to "filter this list as the user types," and there's a decent chance you get a straightforward .filter() that re-scans the entire array on every keystroke, sometimes nested inside another .find() for a lookup. Clean, readable, and quadratic. It's fine at 50 rows. At 5,000 rows in a searchable table, typing starts to feel like wading through mud, and the code looks completely innocent the whole time.
The re-render trap. This is the fundamentals gap that costs the most and gets talked about the least. AI-generated components love to create new objects, arrays, and inline functions on every render: a fresh {} passed as a prop, a new arrow function defined inside JSX. Each one silently breaks React.memo, useMemo, or useCallback further down the tree, because those optimizations rely on reference equality, not "looks the same to a human." The result is a component tree that re-renders far more than it should, and the code reads exactly like every other React tutorial you've seen. Nothing about it screams "bug."
The race condition trap. The one from the top of this article. Two async calls, no cancellation, no request ordering, and the UI just shows whichever response happens to land last. It requires understanding the event loop and the async runtime to even suspect this as the cause, let alone fix it properly with something like AbortController or a request id check.
The waterfall trap. Ask for "a component that shows the user's profile and their recent orders," and you'll often get one useEffect that fetches the profile, and a child component that fetches orders only once it mounts, after the profile arrives. Two requests that could run in parallel now run one after another, and your "fast" page has a network waterfall baked into its component structure. It looks like good component decomposition. It performs like a queue.
The pattern across all four: the code looks fine. That's the actual danger of AI-generated code. It's not sloppy looking, it's confident looking. Bad code that looks bad gets caught in review. Bad code that looks great is what makes it to prod and shows up three weeks later as "the app feels laggy" with no obvious cause.
The real cost isn't the bug. It's the debugging loop.
Here's the part that doesn't get talked about enough: the fundamentals gap doesn't just cause more bugs, it makes every bug take longer to fix, because it changes how you debug in the first place.
Without a mental model of what's actually happening, debugging becomes pattern matching against whatever the AI suggests next. Paste the error, get a fix, try it, it doesn't work, paste the new error, get another fix. It's not an unreasonable strategy, it's just the only one available when you don't have a theory of the system. But it's a random walk, and random walks are slow and don't teach you anything on the way.
With a mental model, debugging becomes a search instead of a guess. You don't need to already know the answer, you need to know where the answer probably lives. "This only breaks after a few rapid clicks" points at a race condition. "This gets slower the longer the tab stays open" points at a memory leak or an uncleaned subscription. "This is fast alone and slow inside the list" points at complexity or unnecessary re-renders. That instinct, converting a symptom into a hypothesis, is the single highest leverage thing CS fundamentals buy you, and it's exactly the thing AI can't do for you, because it doesn't have your running app in its head. You do, or you should.
This was never really about algorithm interviews
I want to be clear about what I'm not arguing. I'm not saying grind LeetCode, memorize 20 patterns, and recite Big-O on command. That version of "fundamentals" was always a narrow, interview-shaped slice of the real thing, and it's fair to be tired of it.
What I am arguing is that fundamentals are a vocabulary for reasoning about systems, and vocabulary is exactly what determines the quality of questions you can ask, whether you're asking a senior engineer, a textbook, or an AI model. "Why does this feel laggy" is a weak prompt. "This re-renders on every keystroke because the filter function gets a new reference each render, how do I memoize this without breaking the prop it depends on" is a strong one, and you can only write the second version if you already understand the first. AI didn't remove the need for that vocabulary. It raised the price of not having it, because the distance between a vague question and shipped code is now about eight seconds instead of two days, with a lot less friction to catch you on the way down.
What actually got commoditized
Typing components fast was already becoming a commodity skill before AI showed up. Component libraries, design systems, and copy paste culture had been chipping away at it for a decade. AI just finished the job in one hard swing. What's left standing, and what's now more valuable because it's scarcer, is:
- Judgment, knowing which of five working solutions is the right one for this app, this team, this scale.
- Architecture, knowing where state should live and how data should flow before you've written a line, because moving it later is expensive.
- Debugging instinct, turning a vague symptom into a specific hypothesis, fast.
- Performance intuition, knowing that "it renders" and "it renders well" are different claims, and knowing how to tell them apart.
Every one of those is downstream of understanding how the browser, the JS runtime, and your framework actually behave. None of them show up in a single AI-generated diff. They show up in the decisions around it, the ones only you can make, because you're the one who has to live with the app after it ships.
If you're actually deciding what to learn
Skip the trivia. Prioritize the mental models that keep paying off no matter which framework is trendy this year.
- Data structures and complexity, not to recite them, but to eyeball whether a list operation will fall over once real data hits it.
- The JS event loop and async behavior, the difference between a five minute fix and a five hour mystery for anything involving fetches, timers, or effects.
- Memory and closures, why listeners, subscriptions, and timers need cleanup, and what happens when they don't.
- Browser rendering basics, what triggers a re-render, a reflow, or a repaint, and why some "small" UI changes are expensive.
- System and component design, how data should flow through a tree, and why that's a decision worth making on purpose instead of by accident.
None of this is about being anti-AI. Use the tools, they're genuinely great at what they're great at, and pretending otherwise is its own kind of foolishness. Use them to move faster on the parts you already understand. Just don't mistake the speed of the output for the depth of your understanding of it. Those are two different curves, and a lot of people are watching the first one climb and assuming the second one is coming along for the ride.
It isn't. It never was. AI didn't kill CS fundamentals. It just made it louder, and faster, when you don't have them.