Working with AI: Patterns and Best Practices
Introduction
In this post, I would like to present a comprehensive overview of best practices and patterns for working with AI in software engineering. This is by no means an exhaustive list, but it should give you some sound advice, and point you in the right direction. I will keep everything relevant and (mostly) tool agnostic. Also, I linked high quality sources at the end of this post.
How AI works
We should probably get the basics out of the way, and stick to a very high level overview. Even if we don't go into too much detail, this is still valuable information, because it helps you understand AI's limitations.
- At its core, AI is based on machine learning.
- AI models are trained on massive datasets to find patterns in data. These massive datasets are usually large public (and sometimes not so public) parts of the internet.
- LLMs (Large Language Models) encode language statistically, not semantically.
- They generate text one token at a time, predicting each based on prior context. For example, "Big Ben is located in" would likely be followed by "London".
- Wrappers are software layers that simplify interacting with LLMs. As an example, GPT-5 is the model, and ChatGPT is a wrapper around it.
- Wrappers handle prompts, API calls, and responses programmatically.
- Since LLMs only create one token at a time, wrappers keep calling the model until a full response is generated or a stopping condition is met.
- LLMs pick the most likely continuation given the input and context.
- This makes answers "plausible" and "probable" rather than "correct" or "optimal".
- It also leads to hallucinations, where the model invents facts or details. They just tell you what sounds about right.
- Other limitations of AI include bias based on data sets, and sensitivity to prompt phrasing.
Here is a simple diagram of text generation with LLMs:
Diagram based on Figure 2-3, "Prompt Engineering for Generative AI" by James Phoenix and Mike Taylor.
The most important point here is that AI generates plausible continuations based on patterns in their training data. They do not "know" anything, and they do not "understand" anything. What does this mean for us as developers? That means that AI will find bugs that are not bugs. It means that AI will make architectural decisions that are suboptimal but sound great. It means that AI will invent APIs, functions, or libraries that do not exist. It means that AI will drift towards the most common solution, not the best one. It basically means that you should trust your own judgment more than some probabilistically generated text.
The Core Principles of Prompting
Some core principles make successful prompting much easier. There is, of course, a lot of nuance to prompting, based on the tool, model, and use case. Still, the following principles will serve you well in most situations:
- Keep your contexts focused.
- Be very specific about what you want.
- Tell the AI what you want, not what you don't want.
- If context drifts or goes bad, start over. Fixing contexts is either very hard or impossible.
- To share context between sessions, dump summaries into files for the next session to read. You may even keep those files as specs or documentation.
- You may also use files to pass information from one model or agent to another.
- Learn prompt engineering patterns and avoid anti-patterns to greatly improve your results.
- AIs are great at writing prompts. Use tools such as the OpenAI prompt optimizer.
The Core Principles of Coding with AI
While coding with AI is also just prompting, there are some additional principles and rules of thumb that will serve you well. These work in combination with the prompting principles above.
- Always review AI code.
- Never approve code you do not understand.
- Use AI to automate and learn, but never outsource your thinking.
- Divide and conquer. Work in small, iterative steps and clear contexts regularly.
- Isolate changes in Git with small and focussed commits. You may keep those local and combine them later.
- Leverage AI to generate documentation, comments and ADRs. Make it relevant and concise.
- Reuse effective prompts (as agents or slash commands) and share them with your team.
- In pull requests, make crystal clear what was written by AI and what was written by you.
Why Patterns Matter
Do you remember the first time you learned about software design patterns? Reusable solutions to common problems are not only powerful, but they also give you a shared vocabulary and a solid tool set to tackle problems. It's the same with AI prompting and workflows. Once you learn some prompting and workflow patterns, you will start to use, combine, and appreciate them. You will probably also start to invent new ones and discover anti-patterns.
Workflow Patterns
Unlike "real" vibe coding, where AI takes the wheel and writes production code and business logic (oh, the horror), the following workflow patterns actually work. They are not mutually exclusive, and you can combine them as you see fit.
π§ͺ AI as a Prototyper
Let an agent create prototypes of features, components, or even architectures. Review and validate the output, and then write the real solution with the insights you gained. This allows you to quickly explore and validate ideas. Both prototypes and proofs of concepts have their place. In the past, writing throw-away code like this was pretty time-consuming. Now, it can just run in the background while you're doing other things. Be aware, though, that you're missing out on experiences gained from actually writing code yourself and solving nail-bitingly frustrating problems.
When to Use
Use this pattern to quickly test something or explore new ideas.
What it Solves
The prototype pattern enables rapid experimentation and quick feedback loops.
Example
I am working on a color picker based on the design you can see in the screenshot I attached.
Please write a prototype for it.
- Do not use any external libraries
- Do not write any tests
- Prefer straightforward implementations
- Put everything in one file so that it can be easily reviewed
βοΈ AI as a Drafter
Let the AI create a first draft of code, tests, documentation, or architecture. Then, review, validate, and refine the output. This pattern is great to get you started, but remember that AI output is often of lower quality than it initially seems. This is similar to the prototype pattern. The difference is that the output is not deleted, but refined until it's production-ready.
When to Use
You can use this pattern when you need a starting point.
What it Solves
This pattern helps you write less boilerplate and accelerates initial development.
Example
I am working on a color picker based on the design you can see in the screenshot I attached.
Please generate the component and write tests for it.
π¨βπ» AI as a Developer
Again, this pattern seems to be similar to the previous one. However, it really isn't. In this pattern, you work in tandem with the AI. For example, you could write a component and then hand over test generation to the AI. Then, when you're done, you could ask the AI to finish some parts of your implementation while you focus on reviewing and refining the tests. It's all about collaboration and parallelization and can speed you up considerably.
There is also a twist to this workflow pattern. Developing together with AI is called being a "conductor", whereas using asynchronous development (e.g., using the OpenAI Codex web interface to let it create a branch, feature, and a subsequent pull request) is called being an "orchestrator". In general, easy and tedious tasks could be handed over to an AI completely. Overhead like Git management is easy, but it takes time. So fixing a typo or adding a simple alert box could very well be automated.
When to Use
Use when you want a collaborative and parallel workflow with split responsibilities.
What it Solves
This helps you to ramp up your productivity while maintaining control over critical parts.
Example: Conductor
While developing, in your CLI:
I am done with the color picker component. Please write tests for it.
Example: Orchestrator
In your agent's web interface:
There is a typo in our dashboard, it says "dahsboard". Please fix this.
β AI as a Validator
Use AI as validator. Let it run tests, check for OWASP top 10 vulnerabilities, point out flaws in your logic, or identify edge cases and bugs you might have missed. Unlike a full-blown code review, validation happens iteratively while you're developing. It's very useful and allows you to catch problems early.
When to Use
Use this pattern to continuously validate code quality, security, and correctness during development.
What it Solves
Catch regressions, vulnerabilities, edge cases, and mistakes early.
Example
I am done with the color picker component.
Do you see any problems concerning accessibility?
π€ AI as a Sparring Partner
Use AI as a sparring partner to brainstorm approaches, ideas, or solutions. You can even have dialogs to refine your understanding of a problem space. For me, it works well to initially ask for 3 to 10 different approaches, complete with pros, cons, and a final recommendation. Ask the AI to critically challenge their own output, and use options like Claude's "ultrathink" or Amp's "the Oracle" to massively improve quality here.
When to Use
Use the sparring partner pattern to broaden your perspective and explore diverse solutions.
What it Solves
This solves the need for creative ideation and critical evaluation, while also highlighting trade-offs and questioning assumptions.
Example
Please evaluate what the best way of creating a color picker in Svelte is.
I attached an image where you can see the design specification.
Present me with at least three different alternatives. List pros and cons and give me a final recommendation.
Critically evaluate your output before giving me the results.
π AI as a Documenter
AI is very good at generating documentation. However, the output has to
be read and refined. No one wants to read AI slop. Its output is usually
very bland and wordy. Also, it has way too many dashes (β), and,
sometimes, emojis. Still, generating DocBlocks, ADRs, README files, or even user
documentation, can save you a lot of time. As a tip, you can use the outcome of
sparring sessions as the basis for ADRs.
When to Use
Use the documenter workflow pattern when you need readable, structured documentation, ADRs, or docblocks generated from code or design documents.
What it Solves
This helps to maintain and create comprehensive docs.
Example
Please generate an Architecture Decision Record (ADR) for the color picker component we just built, based on our previous sparring session (see
./tmp/SPARRING.md).
π§ AI as a Reviewer
Your AI can do comprehensive code reviews. The quality of code review varies wildly based on the tool, model, prompt, and context. So please, be highly critical of anything the AI spits out. Still, reviewing your own code before submitting a PR is good practice and helps you catch things that static analysis misses. Remember to write regression tests.
When to Use
Use AI for code reviews, to surface obvious issues, or to augment human reviewers with additional perspectives.
What it Solves
Speeds up review cycles and sometimes catches bugs and issues even multiple pairs of eyes can miss.
Example
Perform a code review of pull request #245.
π AI as a Debugger
Scanning long log files for the error message you need has never been easier.
Overall, AI is great and debugging. However, many developers just roll with
that and let AI fix their bugs. This is a bad idea. You could learn so much
from how they do it. Which sed commands do they use to scan the code?
How did they arrive at the solution? What edge cases did they consider?
So I think this is very important advice I can give you:
Don't just ask AI to fix your bug. Ask it to tell you how they fixed it.
When to Use
Use this when you need help.
What it Solves
This speeds up debugging and especially diagnostic steps. Very suitable for learning, improving debugging skills, and gaining detailed knowledge of a system.
Example
My color picker tests are red and I don't understand why.
Please help me fix this issue.
Explain your debugging process to me. I want to learn how to debug such issues efficiently.
Prompting Patterns
While workflow patterns describe high level approaches of working with AI, prompting patterns are about actual work. As mentioned before, there is a lot of nuance, and they can be combined, altered, and extended as necessary.
πͺ Gatekeeper or Guardrail Pattern
Don't you just hate it, if AI "solves" every TypeScript type issue by adding as any?
This is one of the most useful patterns, and I use it daily. The idea is that you give the AI tools and strategies to validate its output. It's usually about asking the AI to keep working until all tests are green and all linting errors are fixed. The metaphor here is that the agent is driving with a guardrail on the side of the road, or that there's a gatekeeper checking if the agent is allowed to proceed.
Sometimes, if you have too many guardrails, agents tend to forget one or two of them.
Therefore, I started to add aggregate check commands, e.g. pnpm run validate or
make validate. Those commands run linters, tests, type checks, and builds in one go.
When to Use
Use this pattern to ensure that AI meets a specific quality standard. This pattern is especially useful if you have tests, linting, or other forms of automated validation in place.
What it Solves
This pattern prevents the AI from delivering broken or low-quality work.
Example
Please fix the linting error in
./src/lib/example.ts(line 160).To verify your solution is working as intended, please make sure the following checks pass:
pnpm run testpnpm run lintpnpm run build
π΅πΌββοΈ Agent Pattern
This is another common pattern, and it works very well. By giving the AI a specific role, you can tweak the possibility for higher quality output. For example, if you just ask the AI to set up a React project, you might get a very basic setup. But if you tell the AI that they are "an experienced React developer", this might yield much better results.
Another example is that a regular developer might have less emphasis on A11Y when developing a critical UX component. Assigning the role of an UX engineer might help to improve the focus on accessibility and design.
Yet another example is that a regular developer may want the most pragmatic way to implement a feature, while a security expert might focus on, well, security.
When to Use
Use this pattern to set broader context and influence the style, quality, or approach of the AI.
What it Solves
This pattern forces AI to adopt a specific mindset or approach.
Example
You are an experienced React developer.
Please set up a new React project considering best practices for performance, accessibility, and SEO.
βοΈ Chain-of-Thought Pattern
AIs are black boxes. You give them a prompt, and they return an answer of varying quality. The Chain-of-Thought pattern helps us understand the reasoning behind the answer, and sheds some light on what was actually going on. By asking the AI to explain its reasoning step-by-step, we can gain insights into its thought process and identify potential problems with or flaws in its logic.
As a side note, if you ever wonder how AI arrived at a certain conclusion, just go and ask it.
When to Use
Use this pattern when you want to understand how an AI arrived at a specific conclusion or answer.
What it Solves
This pattern helps you uncover flaws, biases or mistakes in the AI's reasoning. It may also teach you something about how to approach a certain problem.
Example
Do you like the architecture I laid out in
./docs/architecture.md?Please explain your reasoning step-by-step for the solution you provide.
π Monologue Pattern
Unlike the chain-of-thought pattern, where the AI explains its reasoning to help you understand what's going on, the monologue pattern is about letting the AI doing some kind of rubber duck debugging. By verbalizing its questions, the AI itself broadens its contexts and may arrive at better solutions.
When to Use
Use when you want the AI to externalize AI's internal questioning.
What it Solves
Helps models to arrive at better solutions by forcing them to surface and resolve ambiguities or missing information.
Example
There is a problem with my message queue, some messages are consumed twice.
How can I solve this problem? If there is something you don't know, ask yourself relevant questions and answer them subsequently.
π€ Critic Pattern
AIs really want to please us. That's a problem, because they will often agree with us when they shouldn't, try not to challenge our assumptions, or be very permissive in code reviews. There are two ways to counter this. The first one is to tell them to be super strict. The other solution is to, well, "ask for a friend".
When to Use
Use when you need a strict, adversarial review or when the model's natural tendency to agree would reduce critique value.
What it Solves
Reduces overly permissive or agreeable responses by pushing the AI to be contrarian, stricter, or more exacting.
Example: Say What You Want
Please review pull request #123. Be very strict, I need this to be perfect and I don't mind criticism.
Example: Asking for a Friend (i.e. Lying)
Please review this code snippet my colleague wrote.
πͺ Comparison Pattern
Using a comparison pattern means that you ask the AI to compare two or more approaches or solutions to a problem. This is very useful when drafting or prototyping, as it allows you to explore different options and evaluate their pros and cons. Keep in mind that the AI will never know all the context you know, so be sure to review the output critically.
When to Use
Use this pattern when choosing between multiple design or implementation options.
What it Solves
The comparison pattern highlights trade-offs as well as pros and cons. It also supports informed decision-making.
Example
I want to improve the A11Y of this component.
Please evaluate three to five different approaches.
Recommend the most promising one and explain why you like it.
π― Zero Shot, One Shot, Few Shot Pattern
Examples make your prompts better. Sometimes, omitting examples makes sense, too. In general, if you want some creative ideas or simply brainstorm a bit, zero-shot prompts work well. Zero-shot means that you give no examples at all. If you want the AI to follow a specific format or style, one-shot or few-shot prompting is the way to go. One-shot means giving one example, and few-shot means giving a few examples. Sometimes one example is enough. But for complex tasks, try to help the AI out as much as you can. It's similar to writing tests for your agent.
When to Use
Use Zero-Shot for creative or open-ended tasks. Use One-Shot or Few-Shot when you need AI to follow a specific format or style.
What it Solves
This pattern not only solves the need for reproducibility and format adherence, but also allows for creativity when needed.
Example: Zero-Shot
Please generate a color picker component in Svelte based on the design I attached.
Example: One-Shot
Please generate a color picker component in Svelte based on the design I attached.
Here is an example of a button component in Svelte:
{code_snippet}
Example: Few-Shot
Please generate a color picker component in Svelte based on the design I attached.
Here are some examples of a button component in Svelte:
{code_snippet_1}{code_snippet_2}{code_snippet_3}- ...
πͺ’ Context Pattern
It may seem obvious, but just the right amount of context will go a long way. It's easy to assume that more is always better, but that's not the case. Too much context may lead to confusion, contradictions, or dilution of the main point. Or, in some cases, you may just run out of available tokens. Therefore, be very deliberate about the context you provide. Be as specific as possible, but also narrow down context if necessary.
When to Use
Use this to provide AI with relevant background information.
What it Solves
Prevents context overload, contradictions, mistakes, as well as token and time waste. At the same time, it increases relevance and accuracy of your output.
Example
Please review pull request #123.
Use the GitHub CLI to read all comments, but only consider unresolved issues.
π§πΌββοΈ Meta Prompt Pattern
Meta prompting is a technique in which a prompt leads the AI to generate or refine another prompt. AIs are insanely good at writing prompts. Meta prompting often reveals weaknesses in your own prompts. So this is less about handing over even more work to the AI, and more about improving the quality of your prompts.
When to Use
Use this to improve your prompts to ultimately get better results.
What it Solves
This pattern improves prompt effectiveness and uncovers weaknesses.
Example
Please optimize the following prompt for me:
Write an application that generates beautiful butterfly SVGs.
ππΌββοΈ ReAct (Reason & Act) Pattern
This is a more advanced prompting pattern that combines reasoning and action. Get the model to think and produce some required output. But also tell it how to act while doing so. Claude Skills make this pattern more implicit, and I expect them to become more prevalent in the future. In any case, this is a very powerful pattern.
When to Use
Use the ReAct pattern for complex tasks that require a combination of reasoning and concrete actions (tests, code changes, queries).
What it Solves
This pattern enables AI to reason through problems while simultaneously taking specific actions.
Example A
Use TypeScript to determine the current weather in Kiel (Germany) and print it.
Example B
First write a test, then write the class I mentioned, then adjust the code accordingly.
Anti-Patterns
It's not only important to know what works, but also what doesn't work. Here are some common anti-patterns that can make your life misΓ©rable when working with AI.
π€¦πΌββοΈ Delegating reasoning to AI
Do never let AI make architectural, algorithmic, or product decisions.
At least not without verifying the reasoning. AI produces the most plausible or
probable answer and not the best one. So, if you let AI take the wheel,
do not be disappointed if when your mileage varies.
πͺΊ Prompt Sprawl
Prompt sprawl is the practice of incrementally adding more and more context to a prompt
until it becomes bloated, unfocused, or contradictory. A polluted context leads to very
poor results. Regularly use /clear slash commands or start new threads to keep contexts
clean. If you want to keep some context, you can tell AI to dump summaries into files
for the next session to read. You may even keep spec.md files, it doesn't have to be
a temp.txt file.
ποΈ Context Drift
Drifting context is when the model loses alignment with your current goal,
because the instructions have evolved mid-thread. This often happens when you start
a new task in an existing thread without resetting the context. It's a similar
problem to prompt sprawl, but more subtle. If in doubt, start a new thread or
/clear the context.
π Over-Trusting
Always validate the output of AI. Never fully trust it. As we learned, there will probably always be hallucinations, mistakes, or bad training data. So, double-check the output of AI, especially when it's something critical like security, privacy, or business logic.
MCPs
Now that we're done with patterns and anti-patterns, let's have a look at actual tools that can help us improve our AI experience.
The open source standard Model Context Protocol (MCP) is very useful and very dangerous at the same time. Just google for "MCP Security". However, I still encourage you to try them out, as they can improve your results quite a bit. For example, there are MCPs that help agents understand your code better (e.g. Probe), work correctly with frameworks (e.g. Svelte's MCP), and other handy things (e.g. Asana, Jira, or GitHub). Just consider that you should focus on trustworthy MCPs and that having various MCPs active at the same time can consume a lot of tokens quickly. Finally, I would like to give a special shoutout to the Playwright MCP, which allows agents to interact with web pages directly. This makes frontend development and general debugging of web apps much easier. It also allows for some interesting automation tasks.
MCPs I Can Recommend
I use a lot more MCPs, but those three continue to impress me the most:
Where can I find more?
Just visit punkpeye/awesome-mcp-servers
and enjoy the ultimate MCP server shopping experience.
Hallucinations
There are many reasons to distrust AI output, but hallucinating is probably the most important one. Sometimes, AI will give you an answer that sounds perfectly reasonable, but is actually wrong. Some of of these wrong answers stem from the way tokenization works (e.g. ask an AI how many "e" characters there are in "strawberry" and it may answer "5"), while others derive from the fact that AI produces the most probable and not the most correct output. Aks ChatGPT to give you a list of books on a certain topic, and it may give you book titles that only sound real.
The probability of hallucinations will never be zero, and each new improvement in this area is considerably harder than the previous one. However, there are some strategies that help you mitigate this problem a little.
General Ways to Reduce Hallucinations
Funnily, an easy enough trick is to add "If you don't know, say you don't know" to your prompt. This is not 100% percent safe, but it works more often than you'd expect.
Reduce Hallucinations During Research
A good way to fight hallucinations during research is to do it in several steps. First, ask for a list of articles. I can recommend Perplexity as a tool for this. Then, ask AI your questions. Add "If the answer is not found within these articles or PDF documents, reply with 'I do not have the answer'" to your prompt.
Reduce Hallucinations During Coding
When writing code, it's best to let AI validate it's output. The guardrail pattern (i.e. using tests and static analysis for validation) is very effective here. It becomes more difficult with CSS and UX changes. However, here you can ask AI for a Playwright test. Or you could open your Chrome dev tools, right click on an element and select "Ask AI" to get Gemini's help directly in the browser.
Agents
So, what are some powerful AIs that I can recommend? I played around with many of them, and I assume I didn't invest enough time into them all to make this "fair". However, my short and subjective answer is that Claude Code, Amp, and CodeRabbit currently outclass the competition by a fair margin (December 2025).
Conclusion
And that's it. I hope this was a dense and useful overview of working with AI in software engineering. I hope that this was a useful read, and maybe you had some fun, too.
Cheers βοΈ
Sources
- π "The AI Pocket Book" by Emmanuel Maggiori
- A great introduction to AI concepts and terminology.
- π "Beyond Vibe Coding" by Addy Osmani
- A brilliant book on working with AI in software engineering.
- π "Prompt Engineering for Generative AI" by James Phoenix and Mike Taylor
- A comprehensive guide to prompt engineering.
- π "Embracing the parallel coding agent lifestyle" by Simon Willison
- Sound and actionable advice on working with AI agents.
- π "Vibing a Non-Trivial Ghostty Feature" by Mitchell Hashimoto
- A fun to read article full of insights concerning AI assisted coding.
- π "The "Trust, But Verify" Pattern For AI-Assisted Engineering" by Addy Osmani
- A great article on trust and guardrails.
- π "Conductors to Orchestrators: The Future of Agentic Coding" by Addy Osmani
- A post on synchronous and involved vs. asynchronous and orchestrating AI workflows.
- π "Claude Skills are awesome, maybe a bigger deal than MCP" by Simon Willison
- Willison's opinion on Claude Skills, which I share.
- π "The βSβ in MCP Stands for Security" by Elena Cross
- There is a lot of sound reasoning and advice in this post.