AI Prompt Engineering

Making ChatGPT Write Idiomatic Code in Your Stack, Not Generic Code

June 19, 2026 9 min read 1 views

You ask ChatGPT to write a Django view, and it gives you a function-based view with HttpResponse even though your entire project uses class-based views and DRF serializers. You paste the output in, rewrite most of it, and wonder why you bothered. The problem is not ChatGPT's ability β€” it is that you gave it no anchor for your stack.

ChatGPT is trained on a massive surface area of code. Without constraints, it gravitates toward the most common patterns it has seen, which are often beginner-friendly examples from documentation and Stack Overflow β€” not production patterns from a real codebase. A few targeted prompting changes fix this reliably.

What You'll Learn

  • How to declare your stack and conventions so ChatGPT treats them as hard constraints.
  • How to use inline examples (few-shot prompting) to lock in your style.
  • How to constrain output shape so you get usable code, not scaffolding.
  • How to handle multi-layer code requests without getting a soup of mixed patterns.
  • The most common mistakes that pull ChatGPT back toward generic output.

Why ChatGPT Defaults to Generic Code

ChatGPT does not know your codebase. When you say "write a function to fetch a user by ID," it fills in every blank with a reasonable default: raw SQL or an ORM pattern it has seen most, a basic try/except, and a return type that works in isolation. That is fine for a prototype. It falls apart the moment your project has opinions.

The model is also subtly optimized to produce output that looks immediately correct to a broad audience. Generic code clears that bar easily. Idiomatic code for your specific stack β€” say, FastAPI with Pydantic v2 models, SQLAlchemy 2.0 async sessions, and a specific error-handling decorator β€” requires context that generic prompts never supply.

The fix is not a magic phrase. It is a structured habit of giving the model enough of the right context before the actual request.

Setting the Stack Context Upfront

Start every session β€” or every new topic within a session β€” with an explicit stack declaration. Think of it as the header comment you wish every repo had. It does not need to be long; it needs to be precise.

A good stack declaration covers:

  • Language and version: Python 3.12, TypeScript 5.4, Kotlin 1.9.
  • Framework and version: Django 5.0 with DRF 3.15, not just "Django."
  • Key libraries: The ones that touch most files β€” Pydantic, SQLAlchemy, Celery, React Query, Zod.
  • Architecture decisions: "We use repository pattern for DB access," "All API responses follow a {data, error, meta} envelope."
  • What to avoid: "Do not use requests β€” we use httpx with async throughout."

Here is a concrete example of a stack preamble you can paste at the start of a session:

Stack context:
- Python 3.12, FastAPI 0.111, SQLAlchemy 2.0 (async), Pydantic v2
- All DB access goes through a repository class injected via FastAPI's Depends()
- Pydantic models use model_config = ConfigDict(from_attributes=True)
- Errors raise HTTPException with a custom detail dict: {"code": "...", "message": "..."}
- Use httpx.AsyncClient for any outbound HTTP, never requests
- Type-annotate everything; no implicit Any

Only write code that fits this stack. Do not use patterns from earlier FastAPI or SQLAlchemy 1.x.

That last sentence matters. ChatGPT has seen far more SQLAlchemy 1.x code than 2.0 code. Explicitly forbidding old patterns stops it from defaulting backward.

Feeding It Your Conventions, Not Just Your Language

Language and framework get you halfway. The other half is your team's conventions: naming patterns, module structure, error handling style, and the unwritten rules every PR reviewer enforces silently.

The most efficient way to transfer conventions is to describe them as rules, not preferences. Vague guidance like "follow best practices" is ignored because best practices are in the eye of the beholder. Concrete rules are not:

  • "Repository methods return None on not-found; they never raise exceptions."
  • "Service layer functions must have a return type annotation. Never return implicit None."
  • "Log at DEBUG level before every outbound API call; log at ERROR on failure with the response body."
  • "Test files follow the pattern test_{module_name}.py and use pytest fixtures, not unittest."

You do not need to list every rule. Pick the five or six that your PR reviewer would flag most often, and put them in the preamble.

If you have been getting code reviews from ChatGPT that feel surface-level, the conventions block is usually what is missing. Without it, the model reviews against general Python style, not your project's actual bar.

Using Inline Examples to Lock the Style

Few-shot prompting β€” giving the model one or two examples before your actual request β€” is the most reliable technique for getting code that fits your patterns. It works because the model tries to continue the style it observes, not just the style you describe.

Paste a short, representative snippet from your actual codebase as a reference:

# Here is an example of how we write a repository method:
class UserRepository:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session

    async def get_by_id(self, user_id: int) -> User | None:
        result = await self.session.execute(
            select(User).where(User.id == user_id)
        )
        return result.scalar_one_or_none()

# Now write a similar method called get_by_email that looks up by email address.

That single example tells ChatGPT more than three paragraphs of description would. It sees the class structure, the injection pattern, the AsyncSession type, the scalar_one_or_none() idiom, and the return type annotation. The output will mirror all of it.

Keep your examples short β€” one method or one component is enough. Long examples add noise and can distract the model from the pattern you are actually demonstrating.

Constraining the Output Shape

ChatGPT often adds things you did not ask for: a __main__ block, a full file with imports you already have, a requirements list, or explanatory comments inside the function body. These are helpful for beginners; they are friction for you.

Tell it exactly what shape you want the output in. Some constraints that work well:

  • "Output only the method body, no class wrapper." Useful when you are adding a method to an existing class.
  • "Do not add imports β€” I will handle those." Stops the five-line import block that collides with your existing ones.
  • "No inline comments. Docstrings at the function level only, following Google style."
  • "Output in one code block. No explanation before or after." Useful when you are in a flow state and do not want to parse prose to find the code.

You can also constrain what the code must not include. If your codebase has a strict rule against print() statements in production code, say so. If you do not want try/except Exception catch-alls, say that too. Negative constraints are often more useful than positive ones because they stop the model's most common shortcuts.

This is closely related to the pitfalls covered in debugging ChatGPT code that silently breaks edge cases β€” many of those silent failures come from unconstrained output that handles only the happy path.

Handling Multi-File and Multi-Layer Code

When you need code that spans multiple layers β€” a model, a repository method, a service function, and a route handler β€” the generic instinct is to ask for all of it at once. That usually produces a single-file mess that mixes concerns.

A better approach is to request each layer separately in the same conversation, letting context accumulate:

  1. Ask for the Pydantic schema or model first.
  2. Say: "Using that schema, write the repository method."
  3. Say: "Using both, write the service function."
  4. Say: "Now write the route handler that calls the service."

Each step uses the previous output as an implicit example. The model knows the type names, the conventions, and the shape of each layer because it just wrote them. You get consistent naming and a correct dependency graph without having to re-explain everything.

If you do need everything in one shot β€” for example, when scaffolding a complete CRUD feature β€” write a single long prompt that separates each layer with an explicit header and ends with "Output each layer in its own labeled code block." This stops the model from collapsing everything into one function.

Common Pitfalls When Prompting for Idiomatic Code

Resetting context mid-session

If you start a new topic within the same session without re-anchoring, ChatGPT can drift. It does not always carry the stack preamble forward when the subject changes significantly. If you switch from writing a repository layer to writing frontend TypeScript in the same session, re-state the relevant constraints for the new context.

Pasting documentation as context

Pasting large chunks of library documentation into the prompt seems helpful, but it often backfires. The model tries to demonstrate everything from the docs, not just what fits your use case. Give it one relevant example instead of a reference manual.

Asking for "best practices" without specifying whose

"Follow best practices" is a trap. It invites the model to substitute its judgment for yours. Use it only when you genuinely have no opinion; otherwise, state the practice explicitly. "Use dependency injection via Depends()" is better than "follow FastAPI best practices."

Not checking for version drift

ChatGPT's training data has a cutoff, and older versions of frameworks are over-represented in that data. Always name the version in your stack declaration, and if the output looks like it belongs to an older API, add: "That uses the old API. Rewrite it for version X." This is especially common with SQLAlchemy, Pydantic, and React.

Treating the first output as final

The first output is a draft. If it is close but not quite right, a short correction prompt is almost always faster than starting over. "Good, but replace the raw SQL with the ORM equivalent and remove the print statements" takes ten seconds and usually gets you to a usable result on the second pass.

These same principles apply when you use ChatGPT for other structured tasks. If you have tried having ChatGPT review a database schema, you know that vague prompts produce vague feedback β€” the same rule applies to code generation.

Forgetting to test what you paste

Idiomatic-looking code is not automatically correct code. Run it, add a type check, and run your test suite before merging. ChatGPT can produce output that looks exactly like your codebase and still gets a subtle logic wrong. Think of it as a fast first draft from a capable colleague who has not run the code either.

For a deeper look at trusting AI-generated code on consequential tasks, the guide on getting ChatGPT to generate accurate data migration scripts covers a useful verification workflow you can adapt.

Wrapping Up: Next Steps

Generic output is a prompting problem, not a model limitation. ChatGPT can produce idiomatic code for almost any stack β€” it just needs the right context delivered in the right shape. Here is what to do starting today:

  • Write a stack preamble for your main project and save it somewhere easy to paste. Include language, framework, key libraries, architecture decisions, and three to five explicit rules.
  • Pick one representative snippet from your codebase for each major pattern you generate frequently. Keep these in a notes file and paste the relevant one when you need that pattern.
  • Add output shape constraints to every code request. At minimum: specify whether you want imports, whether you want comments, and whether you want the full file or just the function.
  • Request multi-layer code one layer at a time in the same conversation, building on prior output rather than asking for everything at once.
  • Always run a type check and your test suite on anything you paste into production code, regardless of how correct it looks.

Once you have a reliable preamble and a library of reference snippets, the time cost of getting idiomatic output drops to near zero. The model does the heavy lifting; you supply the context it cannot invent on its own.

Frequently Asked Questions

How do I get ChatGPT to stop using outdated library APIs in generated code?

Explicitly name the library version in your prompt and add a negative constraint like "Do not use the old SQLAlchemy 1.x session API." If the output still uses old patterns, follow up with "That uses the deprecated API β€” rewrite it for version X."

Can ChatGPT learn my coding conventions across multiple sessions?

Not without a custom system prompt or memory feature enabled. The safest approach is to maintain a saved stack preamble and paste it at the start of each new session so your conventions are always in context.

What is the best way to get ChatGPT to match my project's folder and file structure?

Describe your module layout in the preamble and show one example of how a similar file is organized in your project. Ask ChatGPT to output each file in a separate labeled code block so the structure is explicit.

Why does ChatGPT ignore my framework instructions and write plain code anyway?

Usually because the instruction was too vague or came after the model already started generating. Put all stack constraints at the very top of your prompt, before the actual request, and use specific rule statements rather than general directives like "use best practices."

How many examples should I paste when using few-shot prompting for code generation?

One clear, representative example is usually enough. Two examples work well when you want to demonstrate variation in the same pattern. More than two tends to add noise and can cause the model to average across examples rather than follow a single style.

πŸ“€ Share this article

Sign in to save

Comments (0)

No comments yet. Be the first!

Leave a Comment

Sign in to comment with your profile.

πŸ“¬ Weekly Newsletter

Stay ahead of the curve

Get the best programming tutorials, data analytics tips, and tool reviews delivered to your inbox every week.

No spam. Unsubscribe anytime.