AI-Assisted Coding: Cursor, Claude Code, Copilot Workflow

Since late 2025, some companies hand you an AI assistant in the interview and grade how you drive it. That is how mainstream AI assisted coding has become: the typing is cheap now, and the judgment is the job. This post shows the loop that keeps you safe, spec first, small diffs, verify everything, by extending a real project and catching a planted bug.

“Debugging is twice as hard as writing the code in the first place. So if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”

Brian Kernighan

Last Updated: July 2026 | Tested on: Python 3.14.6 | Difficulty: Intermediate | Reading Time: 18 minutes

Kernighan wrote that line decades before any AI wrote a function for you, and it is the whole reason this topic matters. An assistant writes clever code faster than you ever could, which means the hard part, understanding and debugging it, lands entirely on you. Think of the assistant as a very fast, very confident intern. It types quicker than anyone on the team, it never gets tired, and it will state a wrong answer with exactly the same calm certainty as a right one. You would never let an intern push straight to the main branch without a review, and the same rule keeps you safe here.

AI assisted coding is not a fringe skill any more. A recent Dataiku job posting for a senior Python engineer lists “proficiency with AI assisted development tools” as a plain requirement next to testing and Git, and since around October 2025 several companies, Meta among them, have added an AI enabled coding round where you are handed an assistant and graded on how well you drive and verify it. Almost nobody teaches the actual workflow, so let us fix that.

The Skill Nobody Teaches: Steering, Not Typing

Most people use an assistant like a slot machine: type a vague wish, pull the handle, paste whatever falls out, and hope. That is how bad code sneaks into production. The professional habit is the opposite, and it rests on three moves you already half know from ordinary engineering.

  • Spec first. Before you prompt, write one or two sentences of exactly what “done” means, the same acceptance list you would write for yourself. A vague prompt gets a vague, confident guess back.
  • Small diffs. Ask for one function or one change at a time. A 200 line generated blob is impossible to review honestly, so you end up trusting it, which is the whole trap.
  • Verify before you accept. Nothing the assistant writes is real until a test passes and your linter and type checker stay quiet. You are the gate, not the keyboard.

This is the exact review loop from our code review tutorial, just pointed at a machine instead of a colleague. The reviewer does not care who wrote the line; the standard is the same. The diagram below is the loop you run for every single change, whether you typed it or an assistant did.

looks wronglooks rightall passany failWrite the specwhat does done look likePrompt the assistantone small task at a timeAI returns a diffyou have not accepted it yetRead the diffsmall and on-spec?Run tests plusruff and mypyCommit the changeyou own this code nowReject or re-promptpaste the real errorThe AI-Assisted Coding Loop: Spec, Small Diff, Verify Before You Accept

Read the loop from the top. You write the spec, prompt for one small task, and the assistant returns a diff you have not accepted yet. You read that diff: is it small, and does it do what the spec asked? If it looks wrong, you reject it or re prompt with the specific problem. If it looks right, it still has to pass the gate: your tests, plus ruff and mypy from our code quality tutorial. Only when everything is green do you commit, and the moment you commit, that code is yours. Not the assistant’s. Yours to defend in review and fix at 2am.

A Worked Example: Adding a Report with an Assistant

Let us make this real. In the log parser project we built a small Command-Line Interface (CLI) that reads an Apache access log and reports errors, top IPs, and error spikes. Say a developer named Aviraj wants to add one more report: the most requested URL paths. That is a perfect task to hand an assistant, because it follows a pattern already in the file.

Step one is the spec, written before any prompt. Here is exactly what Aviraj types to the assistant.

📄 the prompt: a spec, not a wish

Add a `paths` report to logparse.py that ranks the most requested
URL paths, most frequent first. Match the existing cmd_top style:
take entries and args, use args.top for how many rows, return a
string, and register it in the COMMANDS dict. Standard library only.

The assistant replies in seconds with a tidy diff. It reads well, it follows the house style, and it is short enough to review. Here is what it proposed.

📄 the diff the assistant generated

+def cmd_paths(entries, args):
+    counts = Counter(e.path for e in entries)
+    top = counts.top(args.top)
+    lines = [f"Top {args.top} requested paths"]
+    for path, n in top:
+        lines.append(f"  {path:<20} {n}")
+    return "\n".join(lines)
+
+
-COMMANDS = {"top": cmd_top}
+COMMANDS = {"top": cmd_top, "paths": cmd_paths}

Look closely, because this is where a slot machine user loses. The line counts.top(args.top) looks completely natural. It reads like real English, it sits inside otherwise correct code, and if you were skimming you would accept it. So do not skim. Run it.

📄 Terminal: run the report before trusting it

python logparse.py paths access.log --top 5

▶ Output

Traceback (most recent call last):
  File "logparse.py", line 91, in <module>
    main()
    ~~~~^^
  File "logparse.py", line 86, in main
    result = COMMANDS[args.command](entries, args)
  File "logparse.py", line 63, in cmd_paths
    top = counts.top(args.top)
          ^^^^^^^^^^
AttributeError: 'Counter' object has no attribute 'top'. Did you mean: 'pop'?

What happened here: the assistant invented a method. collections.Counter has no .top(); the real method for the top n items is .most_common(n), which you used two functions up in cmd_top. This is a hallucinated Application Programming Interface (API), the single most common way AI code fails, and Python caught it the instant the code ran. The interpreter even guessed you meant pop, which you did not. If Aviraj had pasted this and moved on, the crash would have shown up later, maybe in front of a user.

Running it once by hand is good, but the durable fix is a test, so this bug can never come back silently. Write the test before you accept the code, based on the spec, not on what the assistant wrote. Six requests, three paths, ask for the top two: /a should rank first and /c should not appear at all.

📄 test_paths.py: written from the spec, not the code

from types import SimpleNamespace
from datetime import datetime, timezone
from logparse import cmd_paths, LogEntry


def make(path):
    return LogEntry("1.1.1.1", datetime.now(timezone.utc), "GET", path, 200, 100)


def test_paths_ranks_by_count():
    entries = [make("/a"), make("/a"), make("/a"),
               make("/b"), make("/b"), make("/c")]
    args = SimpleNamespace(top=2, json=False)
    out = cmd_paths(entries, args)
    assert "/a" in out
    assert "/c" not in out                      # only the top 2 should show
    assert out.index("/a") < out.index("/b")    # /a is most requested

📄 Terminal: run the test against the AI code

pytest test_paths.py -q

▶ Output

F                                                                        [100%]
================================== FAILURES ===================================
__________________________ test_paths_ranks_by_count __________________________

    def cmd_paths(entries, args):
        counts = Counter(e.path for e in entries)
>       top = counts.top(args.top)
              ^^^^^^^^^^
E       AttributeError: 'Counter' object has no attribute 'top'. Did you mean: 'pop'?

logparse.py:63: AttributeError
=========================== short test summary info ===========================
FAILED test_paths.py::test_paths_ranks_by_count - AttributeError: 'Counter' o...
1 failed in 0.16s

What happened here: the test failed for the same reason the manual run did, which is exactly what you want. Now the fix is one word. Change .top to .most_common, the method that actually exists, and run the test again.

📄 the one line fix

-    top = counts.top(args.top)
+    top = counts.most_common(args.top)

▶ Output: pytest after the fix

.                                                                        [100%]
1 passed in 0.02s

▶ Output: the real report on the 10,000 line log

Top 5 requested paths
  /                    2531
  /api/orders          1933
  /api/login           1579
  /api/products        988
  /static/app.css      785

What happened here: the test passes, the report runs on the real log, and only now does Aviraj commit. The assistant did the boring typing and got the shape 90 percent right, which genuinely saved time. The spec told it what to build, the manual run and the test caught the one thing it got wrong, and the fix took five seconds because the failure pointed straight at the line. That is the loop. Notice that the human never had to be smarter than the assistant, only more careful.

The Failure Taxonomy: How AI Code Goes Wrong

Once you have watched a few of these bugs, you start to recognise families. AI code fails in a small number of repeatable ways, and each one has a cheap catch. Learn the table below and you will spot most problems before they cost you anything.

FailureWhat it looks likeHow you catch it
Hallucinated APIA method or import that does not exist, like Counter.top()Run it once; AttributeError or ImportError fires immediately
Subtle off-by-oneSlicing [:n-1] when it meant [:n]; returns one row shortA test that asserts the count, not just the type
Stale API knowledgeOld syntax or a wrong return type from a changed librarymypy flags the type mismatch before runtime
Overconfident refactorA tidy rewrite that also quietly drops a case or an importruff for dead code, plus the existing test suite

Two of these never reach a human if you have the quality gate from the code quality tutorial wired in. Suppose an assistant refactors that report and, being tidy, adds an import it does not use and returns the wrong type. Here is the kind of snippet it hands back.

📄 ai_snippet.py: an overconfident little rewrite

import itertools
from collections import Counter, OrderedDict


def busiest_path(entries: list) -> str:
    counts = Counter(e.path for e in entries)
    ranked = OrderedDict(counts.most_common())
    return ranked.get("/api/login")

📄 Terminal: let ruff and mypy read it

ruff check ai_snippet.py
mypy ai_snippet.py

▶ Output

F401 [*] `itertools` imported but unused
 --> ai_snippet.py:1:8
  |
1 | import itertools
  |        ^^^^^^^^^
help: Remove unused import: `itertools`

Found 1 error.
[*] 1 fixable with the `--fix` option.

ai_snippet.py:8: error: Incompatible return value type (got "Any | None", expected "str")  [return-value]
Found 1 error in 1 file (checked 1 source file)

What happened here: two tools, two bugs caught, no cleverness required. Ruff spotted the phantom itertools import the assistant sprinkled in for no reason. Mypy caught the real trap: OrderedDict.get returns str | None because the key might be missing, but the function promised a plain str. That is a stale API assumption dressed up as clean code, and it would have handed None to whatever called it. Neither bug needed you to be clever; you just had the gate on. This is why the workflow leans so hard on the same tools you already run for your own code.

The off-by-one deserves one more look, because it is the quietest of the family. It does not crash and it passes a type check, so only a test that checks the actual count will ever see it.

📄 offbyone.py: a rewrite that returns one row short

from collections import Counter

paths = ["/a"] * 5 + ["/b"] * 4 + ["/c"] * 3 + ["/d"] * 2 + ["/e"]
counts = Counter(paths)


def top_paths(n: int) -> list[tuple[str, int]]:
    ranked = counts.most_common()
    return ranked[:n - 1]          # the assistant's off-by-one: should be [:n]


rows = top_paths(3)
print("asked for top 3, got", len(rows), "rows:", rows)
assert len(rows) == 3, f"expected 3 rows, got {len(rows)}"

▶ Output

asked for top 3, got 2 rows: [('/a', 5), ('/b', 4)]
Traceback (most recent call last):
  File "offbyone.py", line 12, in <module>
    assert len(rows) == 3, f"expected 3 rows, got {len(rows)}"
           ^^^^^^^^^^^^^^
AssertionError: expected 3 rows, got 2

What happened here: the code ran happily and returned a list of the right type, so nothing shouted. The n - 1 slice quietly gave back two rows when three were asked for, the sort of thing that looks fine in a demo and is wrong in production. Only the assert on the count exposed it. This is the lesson behind “test the behaviour, not the type”: an assistant is very good at producing code that has the right shape and the wrong answer.

Where AI Wins and Where It Burns Time

An assistant is a power tool, and like any power tool it is brilliant for some jobs and a menace for others. Knowing the difference is most of the skill. Here is the honest split from real day to day AI assisted coding.

  • Wins: boilerplate. Argparse setups, dataclass definitions, a fresh pytest file, a regex for a known format. Repetitive code with a clear shape is where the assistant genuinely saves you minutes every time.
  • Wins: first draft tests. “Write pytest cases for this function” gets you a solid starting set fast. You still read them and add the edge cases it missed, but the blank page problem is gone.
  • Wins: explain this code. Point it at a gnarly function someone else wrote and ask what it does. It is a fast, patient explainer, and you can verify its answer by reading the code it just described.
  • Burns time: fuzzy requirements. If you cannot write the spec, the assistant cannot either. It will produce confident code for the wrong problem, and you lose more time untangling it than you would have spent thinking first.
  • Burns time: large cross file changes. Ask it to rewire five modules at once and you get a diff too big to review, so you either trust it blindly or re read the whole thing. Both are slower than small steps.
  • Burns time: anything novel. A brand new algorithm or a fresh library released after its training cutoff is where hallucinations cluster. When you are on genuinely new ground, the machine is guessing, and you are back to reading the docs yourself.

Common Mistakes

Mistake 1: Accepting a diff you did not read

The one click “accept” button is where most AI bugs enter a codebase. If the change is too big to read, it is too big to accept: ask for a smaller one. The moment you commit, that code is yours to explain in review, so read every line as if you had typed it, because as far as the team is concerned, you did.

Mistake 2: Prompting a wish instead of a spec

“Make the log parser better” gets you a confident guess at what “better” means. “Add a paths report that ranks URLs by request count, top N, standard library only” gets you the thing you wanted. The narrower the spec, the smaller the diff, and the easier it is to verify. Vague in, vague out.

Mistake 3: Trusting confidence as correctness

An assistant states a hallucinated method with the exact same steady tone it uses for a correct one. There is no wobble in its voice when it is wrong. Confidence is not evidence. A passing test is evidence, a clean mypy run is evidence, the docs are evidence. Trust those, not the tone.

Best Practices

  • DO write the spec before the prompt, even one sentence, so you know when the answer is right
  • DO ask for one small change at a time, sized so you can honestly review the whole diff
  • DO write the test from the spec, not from the generated code, so a hallucination cannot pass it
  • DO run ruff and mypy on generated code exactly as you do on your own
  • DON’T paste a change you have not read, no matter how tidy it looks
  • DON’T reach for the assistant on a fuzzy or genuinely novel problem before you have thought it through yourself

The Interview Angle: AI-Enabled Coding Rounds

The newer AI enabled rounds are not testing whether you can type a loop from memory. They hand you an assistant and watch how you drive it. What earns the offer is exactly the loop in this post: you say your plan out loud, you prompt in small steps, and when the assistant hands you code, you read it critically and verify it before moving on. Catching a hallucinated method and fixing it with a test is a strong signal, far stronger than never hitting a bug because you never looked.

What fails the round is pasting generated code without reading it, or freezing when it does not work. Interviewers know the tools produce bugs; they are watching to see whether you notice. Narrate your verification. Say “I will run this before I trust it” and then actually run it. Treat the assistant as a fast junior whose work you own, and you will look exactly like the engineer they want to hire.

Conclusion

AI assisted coding does not replace the skills in this series, it raises the value of them. Reading code, writing tests, and running ruff and mypy matter more now, not less, because they are how you verify a machine that types faster than you and is wrong often enough to hurt. The workflow is simple and it lasts: spec first, small diffs, verify before you accept, and own everything you commit. Cursor, Claude Code, and GitHub Copilot are the names at the time of writing, and by the time you read this some of them may have changed, but that loop will not. The tool is disposable; the discipline is the career.

Next, practice AI assisted coding on one of your own projects: write the spec, ask for one small change, and refuse to accept it until a test passes. And if you want the full path from first steps through AI and machine learning, browse the Python + AI/ML tutorial series home.

Frequently Asked Questions

What is AI assisted coding?

AI assisted coding is writing software with a code generating assistant such as Cursor, Claude Code, or GitHub Copilot, where the tool drafts code from your prompt and you steer, review, and verify it. The skill is not typing prompts, it is writing a clear spec, keeping each change small, and testing every generated line before you accept it.

Does using an AI assistant mean I do not need to learn Python?

No, the opposite. The assistant produces confident code that is wrong often enough to matter, so you need to read code, write tests, and run type checks to catch its mistakes. The tool raises the value of core skills because you are now the reviewer of a very fast, sometimes wrong, junior developer.

What is the most common way AI generated code fails?

Hallucinated APIs: the assistant confidently calls a method or imports a module that does not exist, like Counter.top() instead of Counter.most_common(). These crash the instant the code runs, which is why running generated code once, before trusting it, catches most of them immediately.

How do I catch AI bugs that do not crash?

Use tests and the quality gate. A test that asserts on behaviour catches subtle off-by-ones that return the wrong count without crashing. Ruff catches phantom imports and dead code, and mypy catches wrong return types and stale API assumptions before they ever run.

Will these specific tools still exist in a few years?

The names will change. Cursor, Claude Code, and GitHub Copilot are the leaders at the time of writing in mid 2026, and the market moves fast. What does not change is the AI assisted coding workflow: write a spec, keep diffs small, and verify with tests and type checks. Learn the loop, not the logo.

Is it cheating to use an AI assistant in a coding interview?

Not in the newer AI enabled rounds, where you are handed an assistant on purpose and graded on how well you drive and verify it. In a traditional round without one, follow the instructions you were given. When a tool is allowed, using it well and checking its output is the skill being tested.

Interview Questions on AI-Assisted Coding

Scenario questions, not trivia: this is the form this topic takes in a real interview.

Q: An assistant hands you a 60 line function that looks correct. How do you decide whether to accept it?

First, I would not accept a 60 line diff at all if I could avoid it; I would ask for the change in smaller pieces I can actually review. Given the block, I read every line against the spec I wrote before prompting, run it once to catch any hallucinated call, then write or run tests that assert on the behaviour I expect, not just that it returns without error. Finally I run ruff and mypy. Only when all of that is green do I commit, because the moment I commit it is my code, not the assistant’s.

Q: The assistant used a method you have never seen. What do you do?

I do not assume it exists just because the code reads well; hallucinated methods are the most common AI failure. I check the official documentation for that method, or I simply run the code, since a made up method raises AttributeError immediately. If it is real but unfamiliar, reading the docs also tells me its return type and edge cases, which is exactly what I need before I depend on it.

Q: Why write the test from the spec rather than from the generated code?

If I write the test to match what the assistant produced, I am just encoding its assumptions, including any bug, so the test passes on wrong code. A test written from the spec describes the behaviour I actually want, independent of the implementation. That is what caught the off-by-one earlier: the code ran and returned the right type, but the test asserted the correct row count and failed, which is precisely the mistake I needed to see.

Q: Where does an AI assistant genuinely save time, and where does it cost you time?

It wins on boilerplate with a clear shape: argparse setups, dataclasses, a first draft of tests, and explaining unfamiliar code. It burns time on fuzzy requirements, because it will confidently build the wrong thing, and on large cross file refactors, where the diff gets too big to review honestly. It also struggles on genuinely novel work or libraries newer than its training, which is where hallucinations cluster. The rule of thumb is: clear and repetitive, let it draft; fuzzy or novel, think first.

Q: In an AI enabled coding round, what are the interviewers actually grading?

They are grading how you drive and verify the tool, not whether you can code from memory. They want to see a plan stated out loud, small prompts, critical reading of the output, and real verification: running the code, writing a test, checking the docs when something looks off. Catching and fixing a hallucination is a positive signal. Pasting generated code without reading it, or freezing when it breaks, is what fails the round.

Q: These tools change fast. How do you future proof the skill?

By learning the workflow rather than any one product. Spec first, small diffs, verify before you accept, and own what you commit; that loop works the same whether the assistant is Cursor, Claude Code, Copilot, or something that does not exist yet. The verification tools, tests, ruff, and mypy, are also stable and vendor neutral. When the tool names change, my process does not, which is exactly why I invest in the process.

Further reading: the official Python documentation is the authoritative source on this.

Previous: Python: CI/CD for Python with GitHub Actions, Pre-commit, Automated Testing

Next: Python: Security Basics, Secrets, Input Validation, Dependency Scanning

Series Home: Python + AI/ML Tutorial Series

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *