AI Coding Assistants: How Developers Use Copilot, Cursor & Beyond
Tuesday, 9:40 a.m. You have just inherited the billing module. The two developers who wrote it left the company months ago, and the ticket on top of your queue says, in full: "VAT calculation wrong for German customers. See line 47." You open billing/tax_rules.py — or the Java, Go, or TypeScript equivalent in your world — and find 600 lines of uncommented logic, a regular expression the length of a phone number, and a helper function called doMagic. Somewhere in this file is a bug that is costing real money, and the only documentation is the git history, which ends with the message "fix stuff".
A year ago, your options were: read every line, or find someone who remembered. Today there is a third option, and you are already using it: an AI coding assistant. But there is a gap between having one and using it well. This post closes that gap. It covers how developers actually use these tools day to day: what the tools are, the three modes they operate in, how to write prompts that get useful code, what the tools are bad at, and the review discipline that separates developers who ship with AI from developers who get burned by it. No hype, no productivity statistics — just the craft, grounded in concrete examples.
The tool landscape: four shapes, one idea
Every AI coding tool you will meet is a variation on one idea: a large language model that can read and write code, wrapped in different amounts of context — your files, your terminal, your project — and different amounts of autonomy. Describe them by what they do, not by their brand names, and the landscape sorts into four shapes:
- Inline autocomplete. The assistant lives inside your existing editor and suggests code as you type — sometimes a line, sometimes a whole block. You press a key to accept, another to dismiss. It is the most lightweight form of AI help: zero context-switching, suggestions grounded in the file you are editing. The canonical example is GitHub Copilot in completion mode, but most editors now ship something in this shape.
- AI-native editors. An editor built with AI at its center rather than bolted on: chat panels that can see your whole project, the ability to apply multi-file edits from a single instruction, and the habit of indexing your codebase so answers can reference it. The canonical example is Cursor. The trade-off is real: you get deeper integration, but you switch editors (or at least editor setups).
- Chat-based help. A conversation window — in your editor, in a browser tab, or in a desktop app — where you paste code or ask questions and get answers. No direct access to your project unless you give it files or snippets. This is the mode you reach for when the question is about code rather than in the middle of writing it: "why does this fail?", "what does this regex match?", "which algorithm should I use here?"
- Terminal agents. An assistant that runs in your shell (or in your editor's command palette) and works in a loop: read files, make edits, run commands, check the results, repeat — asking for your approval before it does anything consequential. These are the most autonomous of the four, and therefore the ones that demand the most supervision. Think of them as a junior developer you can hand a bounded task to, with the emphasis on bounded.
The brands will change — tools launch, merge, and rebrand constantly, so check current documentation rather than this post for what any one of them does today. The four shapes are what you need to remember, because each one fits a different moment of your day. That is the next thing to get right.
The three modes: autocomplete, chat, agent
Forget the product names for a moment. When you sit down to work, the assistant is doing one of three things for you, and each one shines in a different situation:
Autocomplete mode — for code you could almost write yourself. Boilerplate you have typed a hundred times, the next lines of a pattern you already recognize, a standard loop, a familiar API call. Autocomplete earns its keep in flow: it removes the mechanical keystrokes and the trips to documentation for things you already know. It shines when you already know what the code should do and just don't want to type it. Decision rule: if you can predict the suggestion before it appears, autocomplete is doing its job — accept it and keep moving.
Chat mode — for questions and isolated tasks. This is where you stop typing and start asking: explain this, debug that, write a first draft of this function, compare these two approaches. Chat mode shines when the hard part is thinking, not typing. It works best on self-contained problems: one function, one error message, one design question. Give it the relevant snippet, ask precisely, and treat the answer as a starting draft, not a verdict.
Agent mode — for bounded multi-step tasks. "Add input validation to the signup form," "write tests for the billing module," "migrate these three functions to the new API." The agent reads files, makes edits across them, and runs things to check its work. It shines when the task spans several files and has a clear, checkable definition of done. Decision rule: use agent mode when you can state the finish line — "all tests pass," "these five call sites are updated" — and can review the whole diff afterward. If the finish line is vague ("clean up the codebase"), the agent will invent its own, and you will not like it.
Most experienced developers use all three in a day: autocomplete running constantly in the background, chat for the moments they stop to think, agents for the chores with a crisp definition of done. The skill is not picking a favorite; it is matching the mode to the moment.
Worked examples: prompts that get useful code
Here is the open secret of coding assistants: the model is only half the equation. The other half is the prompt, and most developers write bad ones — then blame the tool. The fix is a small discipline. A good coding prompt has four ingredients: context (what is this and where does it live?), the task (what should I get back?), constraints (what must it not do?), and success criteria (how will I judge the result?). Three worked examples, each shown as a vague prompt and then its repaired version.
Example 1: "Explain this regex." You found the phone-number-long regex on line 47 of the inherited billing module and need to know what it matches before you touch anything. The vague prompt gets you a vague answer:
Explain this regex:
^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$
That will probably produce a generic walkthrough — correct, maybe, but not aimed at your actual question. The repaired prompt:
This regex validates the email field on our signup form (browser-side,
JavaScript). Break down what each part matches, in plain language. Then
tell me: which valid-looking addresses would it REJECT, and which
invalid ones would it ACCEPT? Flag anything you'd consider risky for a
production signup form.
The difference: the context (browser, JavaScript, signup form) rules out answers about the wrong engine; the explicit request for reject/accept cases aims the explanation at your question instead of the generic one; and asking it to flag risk invites the caution you actually need before editing a validation rule. Note what the prompt does not do: it doesn't ask the model to fix anything yet. Understand first, change second — especially in inherited code.
Example 2: "Write tests for this function." Suppose the billing module has a discount function you now need to change safely. First, get tests around it. The vague prompt:
Write unit tests for this function.
The repaired version:
Write unit tests for the function below. Use [your test framework].
Cover: (1) a normal discount on a standard order, (2) a zero-total
order, (3) a discount larger than the order total, (4) an expired coupon
code, (5) a coupon with a usage limit that has already been reached.
Each test gets a descriptive name. For each one, assert both the final
total AND the error/edge-case behavior. If any edge case is ambiguous,
say so in a comment instead of guessing.
[ paste the function here ]
Two things changed. First, the edge cases are listed explicitly — the model no longer has to guess which ones matter to your business, and it no longer silently skips the awkward ones. Second, "say so in a comment instead of guessing" gives the model permission to be uncertain, which is exactly what you want from a tool that would otherwise invent a confident-looking answer. You are not just asking for tests; you are telling it what thorough means here.
Example 3: "Refactor this." You have a 90-line function and you want it split up. The vague prompt:
Refactor this code.
"Refactor" is doing all the work in that sentence and means nothing specific. The repaired version:
Refactor the function below. Constraints:
- Do NOT change its behavior or its public signature.
- Extract helpers for (a) input validation, (b) the discount calculation,
(c) the tax lookup. Name each helper after what it returns.
- Keep it in [your language]'s standard style; no new dependencies.
After the code, list each change in one bullet, with one line on why.
If you're unsure a change preserves behavior, say so.
[ paste the function here ]
The pattern across all three: the vague prompt describes a topic; the good prompt describes a job — with context, constraints, and the shape of the answer. This takes thirty seconds longer to write and routinely saves thirty minutes of fixing a misfired response. That ratio is the whole argument.
What AI is bad at
Knowing the failure modes is what keeps you fast instead of busy. The assistant has real weaknesses, and they cluster in the same places every time:
- Large refactors across many files. The assistant can hold only so much context, and multi-file refactors live or die on the invariants it can't see: the caller you forgot to mention, the assumption baked into a test fixture three directories away, the deployment script that greps for a string you just renamed. Keep refactors small, verify each step, and treat "I updated everything" claims as unreviewed until you check.
- Your project's conventions. The assistant knows the language's idioms, not your team's. It will happily suggest a library you don't use, a pattern your architecture doc forbids, or an API version you haven't migrated to. Every suggestion has to be checked against your project's way of doing things — the assistant doesn't know it exists.
- Security-sensitive code. Authentication, authorization, cryptography, SQL construction, anything that touches secrets or money. The assistant writes code that looks right — the most dangerous kind of wrong. It has been known to suggest outdated cryptographic practices and plausible-but-unsafe query patterns. This code gets the most review, never the least. If you don't already know how to do it safely, the assistant's output is a sketch to learn from, not a solution to ship.
- Things it cannot verify. It doesn't know your production data, your actual library versions, or whether that API endpoint exists — it will invent APIs, parameters, and version numbers with total confidence. Treat every factual claim in its output (a method name, a config flag, a version number) as a hypothesis to check in the real documentation or the real code, not as information.
Notice the common thread: the assistant is strongest where you can check its work cheaply (does it compile? do the tests pass? is this the idiom my team uses?) and weakest where checking is expensive or skipped. Arrange your workflow so the cheap checks always happen.
The review discipline
This is the section that matters most. Everything above is technique; this is the load-bearing wall. A developer who reviews AI-generated code like a careful colleague can get faster over time. A developer who accepts it on trust will eventually ship a bug that costs more than all the saved keystrokes. The rule is one sentence:
Never commit code you don't understand.
In practice, that means a short checklist, every time:
- Read the diff like a colleague's pull request. Not the prompt you wrote — the code that came back. Would you approve it if a teammate wrote it? If the answer is "I don't fully follow this part," that part doesn't get committed yet. Ask the assistant to explain it line by line, or delete it and write it yourself.
- Run the tests. All of them for the touched area, not just the new ones. AI-generated code fails in boring ways — a misnamed variable here, an off-by-one there — and the test suite is the cheapest net you have. If the area has no tests, that is the first problem to fix (see the "write tests" example above), not a reason to trust the code harder.
- Check what you can't run. Security-sensitive paths, performance claims, API usage against your actual dependency versions — verify against real docs and real code, because the assistant cannot.
- The tomorrow test. Ask yourself: could I defend this code in a review tomorrow, without the assistant beside me? If not, you don't understand it yet — and understanding it is your job, not optional overhead.
One more habit worth building: when the assistant's code fails a test, resist the urge to paste the error back and iterate blindly. Read the error yourself first. Half the time you'll spot the fix in ten seconds, and the habit keeps your own debugging muscles — the ones no assistant replaces — in working order.
Privacy: what leaves your machine
Here is the question developers ask too late: when I accept a suggestion or paste code into chat, where does that code go? The honest general answer: in most setups, your prompt and the surrounding context the tool can see — the open file, often neighboring files — are sent to the provider's servers to generate the response. That is how the tool works; it cannot suggest code it cannot see. The details that matter to you are tool-specific, so check them directly:
- Read your tool's settings and documentation. Look for controls over what context is sent, how long your data is retained, and whether it is used to train models. Defaults change; the settings page is the source of truth, not a blog post.
- Know your employer's policy before you connect a repo. Many companies have rules — sometimes strict ones — about which code may be sent to third-party services. If you are unsure, ask before pasting, not after. Client code, internal tools, and anything under NDA deserve extra caution.
- Minimize before you paste. Strip secrets, API keys, passwords, tokens, and personal or customer data out of anything you send — and better yet, don't include them in the first place. Send the smallest snippet that reproduces the question. This is a standing rule for a reason: redaction after the fact is a hope; minimization is a plan.
- Treat generated code like code from the internet. It may be fine; it may carry assumptions you don't want. License-wise and quality-wise, run it through the same review you'd give a snippet copied from a forum.
None of this is a reason to avoid the tools. It is a reason to use them deliberately — the same way you'd think about what you put in an email to someone outside the company.
Is it actually helping? Measure, don't assume
You will hear big productivity numbers quoted for these tools. Treat any specific percentage with suspicion unless it comes from a study you can read and whose methods you can judge — most of them don't transfer to your codebase, your team, or your kind of work. The only number that matters is yours. And it is easier to measure than you think.
For two weeks, keep a simple log. For each task where the assistant was involved, note three things: the task, where it helped (faster boilerplate, a useful explanation, a test scaffold), and where it hurt (wrong API guesses, a suggestion that compiled but was subtly wrong, time spent debugging its output). At the end of two weeks, look for patterns rather than totals:
- Which modes earn their keep? Many developers find autocomplete saves a steady trickle of time while agent mode is feast-or-famine — brilliant on the right task, expensive on the wrong one. Your log tells you where your line is.
- Where does time leak? The classic leak is accepting a suggestion that looks right, discovering twenty minutes later that it isn't, and debugging code you didn't write. If your log shows that pattern repeating, the fix isn't to quit the tool — it's to tighten the review step from the previous section.
- Measure shipping, not typing. Faster keystrokes mean nothing if the pull request takes longer to review or fails CI more often. The honest metric is time from "I started the task" to "merged, tests green, code I can defend" — and whether that got better.
Be honest with what the log says. If a mode isn't helping, drop it for a month and try again later — the tools improve fast enough that a verdict from six months ago may not hold. The developers who get the most from these tools are not the ones who use them the most; they are the ones who know exactly where they help.
The workflow, in one picture
Everything in this post compresses into a loop. You write the prompt, the assistant suggests, you review — and if the suggestion fails review, you go back to the prompt, not forward to the commit. The tests and the merge are yours:
Back to Tuesday morning and the billing module. The VAT bug is still yours to fix — no assistant understands your company's tax rules. But now you have a way to get oriented: chat mode to explain the regex on line 47 and the purpose of doMagic, a careful prompt to wrap the discount function in tests before you change it, autocomplete handling the boilerplate of the new tests, and your own judgment deciding what gets committed. That is what "using an AI coding assistant well" looks like. Not magic — a loop, with you in charge of every step that matters.
Field check before you move on: take one small, real task from your current work — explaining a function you didn't write is a good start. Write the vague prompt first, then the repaired version using the four ingredients (context, task, constraints, success criteria), and compare the two answers. Then run the two-week measurement log from this post. The tools will keep changing; the loop in the diagram above is the part that stays.
Explore the series: AI Tutorials Hub
Comments
Post a Comment