teachyou.ai academy
← All posts
Claude Code

Claude Code Prompting Patterns That Actually Work

Pramod Dutta · Jun 21, 2026 · 13 min read

Why most Claude Code prompts fail before the model even starts

Most people treat Claude Code like a search bar. They type a sentence, hit enter, and hope. When the output is wrong, they blame the model. But nine times out of ten, the failure happened before generation started — in the prompt itself.

Claude Code is not a mind reader. It is a highly capable engineer with zero context on your codebase, your intent, or your constraints, unless you give it that context. The gap between "meh" output and "ship it" output is almost always the prompt, not the model. This matters more with agentic coding tools than with plain chat, because Claude Code doesn't just answer — it reads files, runs commands, edits code, and can cause real damage if it misunderstands scope.

This article is a practical playbook of prompting patterns that consistently produce better results with Claude Code: patterns for giving context, patterns for scoping changes, patterns for verification, and patterns for multi-step work. Every pattern below comes with a concrete bad-prompt/good-prompt pair so you can see the difference, not just read about it.

Pattern 1: State the goal, not just the task

A task tells Claude what to type. A goal tells Claude what "done" looks like. When you only give a task, Claude has to guess at the goal — and it will guess wrong on anything nontrivial.

Bad prompt:

Add a retry to the fetchUser function.

This is ambiguous in at least four ways: how many retries, what counts as a failure worth retrying, whether backoff is needed, and whether the caller's error-handling contract changes. Claude will pick reasonable defaults, but "reasonable" and "what you actually wanted" are not guaranteed to match.

Good prompt:

The fetchUser function in src/api/users.ts fails intermittently because our
auth provider rate-limits us (HTTP 429). Add retry logic so transient 429s
and network timeouts are retried up to 3 times with exponential backoff
(200ms, 400ms, 800ms). Do not retry on 401/403/404 — those are real errors,
not transient ones. Keep the function signature unchanged since it's called
in 12 places.

Notice what changed: the goal (survive rate-limiting, not "add retries"), the boundary conditions (which errors qualify), and a constraint that prevents scope creep (signature must stay stable). This is the single highest-leverage habit in this entire article — spend the extra fifteen seconds stating what success looks like.

Pattern 2: Give Claude Code the context it cannot infer

Claude Code can read your repository, but it cannot read your intentions, your team's conventions, or decisions that were made in a meeting three weeks ago. If that context matters to the task, it has to go in the prompt or in a CLAUDE.md file.

Three kinds of context are worth explicitly stating almost every time:

  • Why this change is happening. "We're migrating off Redux to Zustand" changes how Claude approaches a component rewrite versus "just make this component simpler."
  • What must not change. API contracts, public exports, database schema, anything another team depends on.
  • What "correct" means for this codebase. Do you use async/await or promises? Class components or functional? Tabs or spaces? If it's not in a style guide Claude can read, say it.

Bad prompt:

Refactor the checkout flow.

Good prompt:

Refactor src/checkout/CheckoutFlow.tsx. Context: we're mid-migration from
class components to hooks, and every new file in this repo since commit
a3f9c21 uses hooks. This file is one of the last holdouts. Convert it to a
function component with hooks, but keep the existing prop interface exactly
as-is — Storybook and three snapshot tests depend on it. Do not touch the
Stripe payment logic in handleSubmit; that code is fragile and out of scope.

This is longer to type. It is also the difference between a 20-minute review and a 2-hour review, because Claude now knows exactly where the fences are.

Pattern 3: Scope the blast radius explicitly

Claude Code's biggest advantage over autocomplete tools is that it can operate across multiple files and run commands. That is also its biggest risk if you don't bound it. A vague prompt on a real codebase can result in Claude "helpfully" touching files you never intended to be part of the change.

The fix is to state scope as a hard boundary, not a suggestion.

Bad prompt:

Fix the bug where dates display wrong in the dashboard.

Claude might fix it in the date-formatting utility (correct, narrow), or it might "fix" every call site that touches dates across the app (broad, risky), depending on how it interprets the report.

Good prompt:

Bug: dates on the dashboard show in UTC instead of the user's local timezone.
Reproduce it at src/dashboard/DashboardHeader.tsx line 42, where we call
formatDate(). Fix this ONLY in the shared formatDate utility at
src/lib/dates.ts — do not modify any call sites. If fixing it there would
break other callers, stop and tell me instead of patching around it.

The phrase "stop and tell me instead of patching around it" is doing real work here. It gives Claude explicit permission to halt and ask rather than silently making a workaround decision on your behalf. This single instruction prevents a huge share of "why did it also change this unrelated file" surprises.

Pattern 4: Use plan mode before touching code on anything nontrivial

For single-line fixes, asking Claude Code to just do it is fine. For anything that touches more than one file, changes a data model, or affects a shared module, ask for a plan first and review it before any code is written.

Bad prompt:

Add multi-tenant support to the billing module.

Handed straight to an agentic loop, this can spiral: Claude starts editing, discovers new information three files in, and quietly changes its own approach mid-flight without you ever seeing the decision get made.

Good prompt:

I want to add multi-tenant support to src/billing/. Before writing any code,
give me a step-by-step plan: which files need to change, what the new data
model looks like, what migrations are needed, and what the riskiest part of
this change is. Do not write or edit any code yet — I want to review the
plan first.

This pattern — plan, review, then execute — catches misunderstandings while they are still one paragraph of text instead of four files of diffs. If the plan reveals that Claude misunderstood "tenant" to mean something different from what you meant, you find out before any code exists, not after.

Pattern 5: Show, don't just tell — use examples

When you want a specific pattern followed, an example is worth more than an adjective. "Clean" and "idiomatic" mean different things to different people; a code snippet means the same thing to everyone.

Bad prompt:

Write a function to validate email addresses in the style we usually use.

Claude has no idea what "the style we usually use" looks like unless it's visible in the repo or you show it.

Good prompt:

Write a function to validate email addresses. Follow the same pattern as
validatePhoneNumber in src/lib/validators.ts — same error-return shape
({ valid: boolean, error?: string }), same JSDoc format, same placement of
the regex as a module-level const above the function.

Better yet, point Claude directly at the file: "match the pattern in X" works because Claude Code can open that file and mirror its structure, naming, and error handling exactly. This is one of the most underused capabilities of agentic coding tools — they're excellent at pattern-matching an existing convention if you point them at a concrete instance of it.

Pattern 6: Ask for the test first, or ask for tests explicitly

Claude Code will often write working code without writing tests unless you ask. And "write tests too" as an afterthought produces weaker tests than asking upfront, because test design shapes implementation design.

Bad prompt:

Implement a rate limiter for the /api/upload endpoint.

Good prompt:

Implement a token-bucket rate limiter for the /api/upload endpoint: 10
requests per minute per user, refilling continuously. Before implementing,
write the test cases you'll use to verify it — include at least: request
under the limit passes, request over the limit is rejected with 429, bucket
refills correctly after the window, and two different users don't share a
bucket. Then implement against those tests.

This does two things: it forces the edge cases into the open before code exists, and it gives you a checklist to verify against instead of just trusting the diff. If your team practices strict test-first development, say so explicitly — write the failing test, then the implementation, then refactor — since Claude won't assume that discipline unless you ask for it directly.

Pattern 7: Correct with specifics, not with "try again"

When output isn't right, the instinct is to say "that's not quite right, try again." This wastes a turn because Claude has no new information to work from — it will often produce a similar or only superficially different answer.

Bad correction:

This isn't what I wanted, please redo it.

Good correction:

Close, but two problems: (1) you're catching the error in fetchUser but
swallowing it silently — it needs to propagate to the caller so the UI can
show an error state, and (2) the retry count is hardcoded to 3 inline;
pull it into a named constant MAX_RETRIES at the top of the file so it's
easy to tune later. Keep everything else as-is.

Specific corrections do two things: they fix the immediate problem, and they teach Claude the actual bar for the rest of the session, which improves everything that follows. "Keep everything else as-is" is also important — without it, a correction can trigger an unwanted rewrite of the parts that were already fine.

Pattern 8: Use CLAUDE.md for context you'd otherwise repeat every session

If you find yourself typing the same context — coding conventions, architecture notes, "never touch this file," deployment steps — into every prompt, that context belongs in a CLAUDE.md file at the project root, not in your prompt each time.

A good CLAUDE.md entry looks less like prose and more like rules a new hire would need on day one:

# Project conventions

- We use Zod for all runtime validation. Never hand-roll validation logic.
- Database migrations live in db/migrations/ and must be reversible —
  every up() needs a matching down().
- The src/legacy/ directory is frozen. Do not edit it, even to fix bugs;
  file the bug instead.
- All new API routes need an accompanying test in the same PR.

This is a prompting pattern as much as a config pattern: it's front-loading context once so every future prompt can be shorter and every future output is more consistent. Teams that skip this end up re-explaining the same constraints in every session, and inevitably forget to mention one of them at the worst possible time — right when Claude is about to touch the frozen legacy file nobody remembers to flag.

Pattern 9: Verify before you trust — ask Claude to prove it, not just claim it

An agent telling you "this works" is not the same as it working. A strong closing pattern for any nontrivial task is asking Claude to demonstrate the result, not just assert it.

Bad closing prompt:

Does this work now?

This invites a confident-sounding "yes" that may or may not be grounded in anything that actually ran.

Good closing prompt:

Run the existing test suite for src/billing/ and paste the output. Then
start the dev server and hit the new /api/tenants endpoint with curl to
confirm it returns 201 for a valid payload and 400 for a missing tenantId
field. Show me the actual output, not a summary.

This forces verification to happen against reality — actual test runs, actual HTTP responses — instead of a model narrating what it expects to be true. The general rule: for anything you'll ship, ask for the artifact of verification (test output, screenshot, curl response), not a description of verification.

Bringing the patterns together in one real prompt

Here's what a strong end-to-end prompt looks like when several patterns stack together — goal, context, scope, example, and verification in one request:

Goal: users report the "export to CSV" button on the reports page silently
does nothing for reports with more than 10,000 rows.

Context: this is likely a timeout or memory issue in
src/reports/exportCsv.ts, since that function loads all rows into memory
before writing. Follow the streaming pattern already used in
src/reports/exportPdf.ts, which streams rows in batches of 500.

Scope: only touch exportCsv.ts and its direct test file. Do not change the
API route or the frontend button component — this should be an internal
fix.

Before writing code: tell me your plan for how you'll convert this to a
streaming approach.

After implementing: run the existing export tests, then write and run a new
test with a 15,000-row fixture to confirm it completes without error. Paste
the test output.

This single prompt encodes almost every pattern in this article, and it reads like a short, well-written ticket — because that's exactly what it is. Claude Code performs best when you give it something closer to a spec than a search query.

Common mistakes that undo good prompting

Even with strong patterns, a few habits quietly sabotage results:

  • Piling on unrelated asks in one prompt. "Fix the bug, also refactor this file, also add tests, also update the docs" spreads Claude's attention across four goals with unclear priority. Split unrelated asks into separate prompts or separate turns.
  • Assuming Claude remembers a decision from three sessions ago. If it mattered, it needs to be in CLAUDE.md or restated. Context windows and session boundaries mean nothing is guaranteed to persist unless it's written down somewhere Claude will read again.
  • Accepting the first working version without reading the diff. "It runs" and "it's correct" are different bars. Prompting well gets you closer to correct on the first try, but reading the actual diff is still your job.
  • Being vague about failure handling. Almost every real bug in agent-generated code hides in the error path, not the happy path. If you don't specify what should happen when something goes wrong, don't be surprised when the answer is "nothing sensible."

Closing: prompting is a skill, and it compounds

The patterns above aren't tricks — they're the same discipline good engineers already bring to writing a clear ticket, a clear PR description, or a clear code review comment. Claude Code rewards that discipline more visibly than most tools, because the gap between a vague prompt and a precise one shows up immediately in the diff you get back.

Start small: pick one pattern from this article — stating the goal explicitly, or asking for a plan before code — and use it deliberately on your next few sessions. It compounds fast once it's habit.

If you want to go deeper on this — structuring multi-file changes, building your own CLAUDE.md from scratch, debugging agentic loops when they go sideways, and working through real prompting exercises rather than just reading about them — that's exactly what we cover, hands-on, in our Claude Code Tutorial for Beginners course on teachyou.ai.