Meta description: I use AI coding assistants every day in real projects. Here’s my hands-on tutorial covering setup, prompting, and the mistakes I made early on.
Last updated: June 21, 2026
The week I stopped fighting my AI assistant and started directing it
My first two weeks with an AI coding assistant were frustrating. I’d accept suggestions blindly, end up with subtly broken code, and conclude the tool “didn’t understand my codebase.” The turning point was realizing the problem wasn’t the model — it was that I was treating it like autocomplete instead of a collaborator that needed context, constraints, and review.
That shift changed how productive these tools actually are for me. This AI coding assistant tutorial covers the exact setup, prompting patterns, and review habits I use now, including the mistakes that taught me each lesson the hard way.
TL;DR
- Context is everything — an assistant given a relevant file and a clear constraint outperforms one given a vague one-line prompt by a wide margin, in my repeated experience.
- Always review generated code for hallucinated APIs — methods or parameters that look plausible but don’t exist in the actual library version you’re using.
- Treat AI-generated code the same as a junior developer’s pull request: read every line before merging, never just before.
Background: why “just ask the AI” isn’t a strategy
Large language models used for coding are trained on massive public codebases, which makes them excellent at common patterns and noticeably weaker on your specific business logic, internal libraries, or unusual architectural decisions. They also don’t know your codebase’s current state unless you give it to them — they generate the statistically most plausible code for the prompt you wrote, not necessarily the correct code for your project.
Security Note: Never paste API keys, credentials, customer data, or proprietary algorithms directly into a cloud-based AI assistant unless your organization has confirmed the tool’s data retention and training policy in writing. I’ve seen teams accidentally leak staging credentials this way.
Once I understood that these tools are pattern-matching against probability, not “understanding” my intent the way a senior engineer would, I started writing prompts very differently — and the output quality jumped immediately.
Prerequisites
- An AI coding assistant installed as an editor extension (this tutorial applies broadly, whether you’re using a chat-based assistant, an inline-completion tool, or an agentic CLI tool)
- A real project to practice on — toy examples don’t expose the failure modes that matter
- Git, so you can review and revert AI-generated changes safely
git status
git diff
I run these two commands constantly while working with AI assistants — they’re my safety net for catching unexpected changes before they get committed.
Step-by-step implementation
Step 1: Set up project-level context
Most modern AI coding tools support some form of persistent project context — a file the assistant reads automatically before generating suggestions. Create one and keep it current.
# Project context for AI assistant
- Language: TypeScript, strict mode enabled
- Framework: Express.js, REST API only (no GraphQL)
- Testing: Jest, all new functions need a corresponding test
- Do NOT use `any` type — use `unknown` and narrow it
- Database: PostgreSQL via Prisma ORM, never raw SQL strings
The first time I added a constraints file like this, the assistant stopped suggesting raw SQL queries that bypassed our ORM’s parameterization — a pattern it had clearly picked up from generic public training data that didn’t match our actual setup.
Step 2: Write prompts with explicit constraints, not just goals
A vague prompt produces vague code. Compare these two approaches I actually tested on the same task:
Weak prompt:
Add a function to validate user emails
Strong prompt:
Add a function `validateEmail(email: string): boolean` in src/utils/validation.ts.
Use the existing `EmailSchema` from src/schemas/user.ts (Zod-based).
Return false on any falsy or malformed input, never throw.
Add a Jest test file covering: valid email, missing @, missing domain, empty string.
The strong version produced working, tested code on the first try. The weak version produced a regex-based validator that ignored my existing Zod schema entirely and introduced a second, inconsistent validation path in the codebase.
Step 3: Use the assistant for explanation before generation on unfamiliar code
When I’m dropped into an unfamiliar part of a codebase, I ask the assistant to explain before asking it to change anything:
Explain what this function does, what calls it, and any side effects,
before suggesting any changes.
This single habit caught a case where a function I almost “simplified” was intentionally mutating a shared cache object — removing that mutation would have silently broken three other call sites. The explanation step surfaced that dependency before I touched a single line.
Step 4: Review for hallucinated APIs every time
This is the failure mode that bit me hardest early on. I asked an assistant to add retry logic to an HTTP client call, and it generated this:
const response = await axios.get(url, {
retry: 3,
retryDelay: 1000
});
This looks completely plausible. It is also wrong — vanilla axios has no built-in retry option; that configuration silently does nothing without the separate axios-retry package. The code ran without errors, logged no warnings, and simply never retried anything. I only caught it because a flaky network test kept failing in CI with zero retries actually happening.
Pro Tip: When an assistant suggests a config option you don’t recognize, search the library’s actual TypeScript types or official docs before trusting it. Hallucinated parameters are syntactically valid and fail completely silently — no IDE error, no runtime crash, just wrong behavior.
Step 5: Use diff-based review, not blind accept
Never accept multi-file AI-generated changes without reviewing the diff exactly as you would a colleague’s pull request.
git add -p
I use git add -p specifically because it forces a hunk-by-hunk review before staging, which has stopped me from committing AI-suggested debug console.log statements and one case of an accidentally hardcoded test API key more times than I’d like to admit.
Step 6: Use the assistant for test generation, then verify coverage manually
I treat AI-generated tests as a starting draft, never a finished suite. The pattern that works for me:
Generate Jest tests for the validateEmail function in src/utils/validation.ts.
Cover: valid inputs, malformed inputs, null/undefined, and the Zod schema's
specific error cases. After generating, list which branches in the function
are NOT covered by these tests.
That last instruction — explicitly asking the assistant to self-report uncovered branches — has caught gaps more reliably than just trusting the generated suite. I then run actual coverage tooling to confirm:
npx jest --coverage src/utils/validation.test.ts
I’ve seen AI-generated test suites that look comprehensive — five or six test cases, clear naming — but still miss an entire branch because the assistant didn’t have visibility into a conditional buried three levels deep in the function. Coverage tooling catches what a quick read-through won’t.
Step 7: Set up a feedback loop for repeated mistakes
If an assistant makes the same category of mistake more than once — say, repeatedly suggesting a deprecated method — I stop correcting it inline every time and instead update my persistent project context file from Step 1 with an explicit rule:
# Known mistakes to avoid
- Do NOT use `componentWillMount` — deprecated, use `useEffect` instead (React 18+)
- Do NOT suggest `request` package for HTTP calls — it's deprecated and unmaintained, use native `fetch` or `axios`
- Do NOT generate `.then()` chains for new code — this codebase uses async/await exclusively
This single habit cut my repeated-correction rate dramatically. Instead of re-explaining the same constraint in every session, the assistant picks it up automatically from project context, which keeps prompts shorter and output more consistent across a team using the same assistant configuration.
Real-world tips I use in production
- I keep prompts task-scoped to one function or one file at a time for anything touching business logic; broad “refactor this whole module” prompts produce code I trust far less.
- For debugging, I paste the exact error message and stack trace, not a paraphrase — the precise wording often contains the clue (line number, exception type) that leads the assistant to the real cause.
- I ask explicitly for edge cases and tests in the same prompt as the implementation; asking afterward in a separate message produces noticeably shallower test coverage in my experience.
- I never let an assistant touch database migration files without an explicit, separate review pass — a malformed migration can be far harder to revert cleanly than a bad function once it’s run against production data.
- For any prompt involving authentication, authorization, or input sanitization, I treat the output as a first draft requiring a dedicated security review, never a final answer — these are exactly the areas where a subtly wrong suggestion has the highest real-world cost.
- I keep a running personal log of hallucinated APIs I’ve caught, organized by library. Reviewing it before starting work on an unfamiliar library takes thirty seconds and has saved me from repeating the same mistake more than once.
[INTERNAL LINK: related article]
Common errors and how I fixed them
Error: Suggested code references a package not in package.json The assistant generated an import for a date-handling library I’d never installed. Running the code immediately threw Cannot find module 'date-fns-tz'. Fix: I now explicitly state “only use packages already in package.json” in my project context file from Step 1.
Error: Generated TypeScript compiles but fails at runtime with undefined is not a function This happened when the assistant suggested a method from a newer version of a library than the one pinned in my package.json. The types resolved fine locally because of a stale @types cache, but the actual installed runtime didn’t have that method. Fix: I now always paste my exact dependency version into the prompt for anything library-specific.
Error: Infinite loop in AI-suggested recursive function A recursive helper function was missing a base-case check for an empty array input. It worked in every manual test I ran by hand, but crashed in production on the first empty-array request. Fix: I now always explicitly ask for “all edge cases including empty, null, and single-element inputs” rather than assuming the assistant infers them.
Error: Generated SQL migration locked a production table for several minutes An assistant suggested adding a NOT NULL constraint directly to an existing column on a large table, without a default value or a batched backfill strategy. Run as-is, this would have taken an exclusive lock on a multi-million-row table during business hours. Fix: I caught it in review before running it, and now I explicitly state “this table has production data, propose a zero-downtime migration strategy” for any schema change prompt.
Error: Assistant suggested logging sensitive fields in plaintext While adding error logging to a payment-processing function, the generated code logged the entire request object, which included a customer’s card details in a test fixture. Nothing malicious — just the assistant pattern-matching against generic logging examples it had seen, with no awareness of which fields in my specific schema were sensitive. Fix: I now explicitly list sensitive field names to exclude in my project context file, and I treat any logging-related suggestion as requiring a manual scan for PII before merging.
FAQ
Q: How do I get better code from an AI coding assistant? A: Give it explicit constraints (language version, existing patterns to follow, libraries already installed) rather than just a goal — vague prompts reliably produce vague, generic code that ignores your actual codebase conventions.
Q: Can AI coding assistants hallucinate library functions? A: Yes, regularly — they generate the most statistically plausible code for your prompt, which sometimes means inventing a parameter or method that doesn’t exist in the actual library, especially for less common configuration options.
Q: Is it safe to use AI coding assistants with proprietary code? A: It depends entirely on the specific tool’s data retention and training policy — check this in writing with your provider before pasting proprietary algorithms or sensitive business logic into any cloud-based assistant.
Q: How do I review AI-generated code effectively? A: Treat it exactly like a pull request from a junior developer — review every line, run git add -p for hunk-by-hunk staging, and specifically check any library function calls against the actual installed version’s documentation.
Q: Why does AI-generated code work locally but break in production? A: This usually traces back to environment differences — a different library version, a missing edge-case the assistant didn’t infer, or a stale type cache masking a runtime mismatch, as I describe in the common errors section above.
Q: Should I let an AI coding assistant write database migrations? A: Treat any AI-generated migration as a draft requiring manual review for production safety — explicitly ask for a zero-downtime strategy on large tables, since assistants don’t automatically know your table size or traffic patterns unless you tell them.
Wrap-up
The biggest shift in my own workflow wasn’t a new tool — it was treating AI-generated code with the same scrutiny I’d apply to any other contributor’s work, while front-loading context and constraints instead of relying on the assistant to guess my intent. Get the prompting habits right and the review habits right, and these tools become genuinely useful collaborators rather than a source of subtle bugs.
Sources: [SOURCE: https://docs.github.com/en/copilot] [SOURCE: https://owasp.org/www-project-top-10-for-large-language-model-applications/]
About the Author
I’m a senior software engineer with over 9 years of experience in backend development and developer tooling, and I’ve integrated AI coding assistants into my daily workflow since they became broadly available. My current stack centers on TypeScript, Python, and Go, and I focus heavily on code review practices and developer productivity tooling. These tutorials come from real prompts, real bugs, and real fixes from my own day-to-day work, not from theoretical best practices alone.

