Blog / AI/ML
AI-Assisted Coding Best Practices: How to Move Fast Without Losing the Plot
Most teams get AI-assisted coding wrong in one of two directions — refusing it, or rubber-stamping everything it writes. A field guide to the workflow that actually holds up under a real code review.
A junior developer joins a team, and within a week they've shipped an authentication middleware, a database migration, and a chunk of payment-handling code — fast, syntactically clean, confidently written. Nobody on the team would dream of merging any of that without review, because a week of tenure buys you exactly zero trust on security-sensitive code. Now swap "junior developer" for "AI assistant," and watch how often that same instinct quietly disappears. That gap — reviewing an AI's output less skeptically than a new hire's, simply because it's fast and confident — is where almost every AI-assisted-coding horror story actually starts.
Two ways to get this wrong, and they're mirror images
The first failure mode is refusing the tool entirely, out of a reasonable-sounding fear that using it will erode your skills. This one is self-limiting — you just move at half the speed everyone around you is moving at, and eventually the market corrects you. The second failure mode is more dangerous precisely because it feels like winning: accepting every suggestion, shipping fast, and quietly losing the ability to explain your own codebase. Six months in, a bug shows up in a file nobody quite remembers writing, in the specific sense that matters — nobody can explain why it works the way it does, only that it did, once, in a demo.
The workflow that actually holds up sits between those two, and it has a fairly simple organizing principle: hand off typing, never hand off understanding.
What's genuinely safe to hand off
Boilerplate is the easy case — route scaffolding, test file setup, config files, the parts of a project that are structurally identical to the last ten projects you built. There's no judgment being exercised when you write a standard Express route handler skeleton for the fortieth time in your career; there's only judgment in what goes inside it, which is exactly the part worth keeping your attention on.
A first draft of something you already know how to evaluate is the second safe case, and it's a subtler one than it looks. If you know what a correct regex for validating an email-shaped string looks like, or what a reasonable SQL query for "top five customers by order count last quarter" should return, you can let an assistant write the first draft and grade it against a standard you already hold in your head. The danger case is the opposite: asking for something you couldn't evaluate if it came back wrong. That's not delegation, that's just hoping.
And translating a decision you've already made into code is squarely in an assistant's strike zone. "Use JWTs with a 15-minute access token and a 7-day refresh token, store the refresh token hashed" is your decision, made with context an assistant doesn't have about your product's risk tolerance. Writing the actual middleware that implements that decision is comparatively mechanical, and handing it off doesn't cost you anything you needed to keep.
What has to stay yours, no exceptions
Architecture decisions can't be delegated because they depend on constraints that live in your head, not in the prompt: what happens when this third-party API is down, whether this data needs to survive a server restart, whether two services are allowed to know about each other's internals. An assistant asked to "add caching" will happily give you a plausible-looking answer without knowing that your team already got burned once by a stale-cache bug in production — you know that; the prompt doesn't.
Anything touching authentication, payments, or personal data needs a human who's actually thought about the threat model, because the failure mode here isn't "the code doesn't work" — broken code fails loudly and gets caught. It's "the code works fine for every case anyone tested, and is subtly, silently exploitable in a case nobody thought to test." That's a categorically different kind of bug, and it's exactly the kind an assistant optimizing for "looks like reasonable code" won't reliably catch either.
Feeding an assistant your codebase's actual conventions
A big, underrated source of bad AI-generated code isn't the model being wrong — it's the model being right about a different codebase than yours. Ask a generic question and you get a generic, technically-correct answer that uses a different error-handling pattern than the rest of your project, a different naming convention, a different way of structuring a response object. It compiles, it works, and it looks like it was written by someone who has never read the rest of your repository, because in a real sense it wasn't.
The fix is cheap and most people skip it anyway: point the assistant at an existing file that already does something similar before asking for new code, or explicitly state the convention — "errors in this codebase are always a `{ code, message }` object, never a raw string" — up front. This single habit eliminates a large share of the "technically works but doesn't fit" code that ends up needing a second pass to clean up, and it turns a generic answer into one that actually looks like it belongs in your project.
A workflow, in the order it actually happens
Decide the shape of the solution before you open the chat, even roughly. "Build me an API for user profiles" and "build me a REST endpoint, `GET /users/:id`, returns `{id, name, avatarUrl}`, 404s with a generic body if the user doesn't exist or isn't visible to the caller" produce genuinely different quality results, because the second one has already made the decisions that would otherwise get guessed at.
Ask for the smallest chunk that's actually useful to review, not the whole feature in one pass. A 40-line diff gets read; a 400-line diff gets skimmed, and skimmed code review is where bugs survive. If you notice yourself about to accept a huge diff in one motion, that's the moment to stop and ask for it in three pieces instead.
Read every line before accepting — not skim for shape, actually read — and when something looks unfamiliar, stop and ask the assistant to explain that specific line before moving forward. This single habit is the entire difference between "I used AI to write this" and "I used AI to help me write this," and it's the sentence you want to be able to say honestly in a postmortem.
Then run it. "Looks correct" and "is correct" are different claims about the same fifteen lines, and only one of them is checkable by reading. Nothing here is optional just because the code compiled — compiling was never the bar.
// A concrete gut-check before you accept a diff:
// Could you explain this block, line by line, to a teammate
// who asked "why did you write it this way" — without
// re-reading it first? If the honest answer is "mostly,"
// that's exactly the block to slow down on.Testing AI-generated code needs its own discipline
AI-generated tests have a specific failure pattern worth naming: they're very good at testing what the code does, and much worse at testing what it should do when reality disagrees with the implementation — which is exactly the case a test suite exists to catch. Ask for tests covering a function and you'll often get thorough coverage of the happy path and the exact edge cases the implementation already handles, with the one edge case the implementation actually gets wrong quietly absent, because the test was generated by looking at the code, not by thinking independently about what could go wrong.
The practical fix is to write the tricky edge cases yourself, by hand, before you ever look at the implementation — empty input, a duplicate ID, a request that arrives after the resource was already deleted — and only then let an assistant fill in the more mechanical coverage around them. Test-first isn't a purity ritual here; it's specifically what prevents an assistant from grading its own homework.
The failure mode nobody budgets time for
The dangerous version of AI-assisted coding isn't using it too much. It's using it silently — reviewing AI-authored code with the same fifteen-second glance you'd give code you wrote yourself an hour ago and still remember every decision behind. You don't have that memory for code you didn't write, which means it needs a slower, more skeptical read than your own code does, not a faster one. Teams that get burned by this almost always describe it the same way afterward: "it looked so clean, nobody thought to question it."
AI drafts, you verify. Shipping code you can't defend, line by line, in a review is the one thing that isn't allowed here — no matter how fast it got you to something that ran.
What changes about code review itself
Teams that adopt AI-assisted coding well tend to change one thing about their review culture: they stop treating "who wrote this" as relevant to how carefully it gets reviewed. A PR is a PR, and it gets read at the same level of scrutiny whether a human typed every character or an assistant drafted most of it and a human verified it — the review bar doesn't move, only the authorship does. Teams that get burned tend to have quietly, informally started trusting AI-authored diffs more than human ones, on the unspoken theory that a model doesn't get tired or careless the way a person does at 6pm on a Friday. It also doesn't know your product, your users, or the outage from eight months ago that taught your team a lesson the hard way — and none of that shows up in a clean diff.
Where this actually leaves your velocity
Done this way, you are still meaningfully faster than writing everything by hand — you've just moved the time you saved from typing into reading, which is a better trade than it sounds like, because reading carefully is the part that was preventing most of your bugs anyway. The teams that get burned by AI-assisted coding aren't the ones that used it heavily; they're the ones that spent every minute they saved on shipping more instead of on reviewing what they'd already shipped.