Active Pairing: How to Lead the LLM
Passive acceptance of LLM-generated code is the fast track to technical debt. The developer prompts a model, gets plausible-looking code, accepts it into the codebase and moves on without fully reading nor understanding it.
To get real value from Large Language Models and tools like Cursor, you need to shift your mindset from “passive consumer” to active pair programmer, which is a completely different way of working.
Unlike pairing with a junior whom you guide and mentor, or a senior from whom you learn, an artificial pair programmer creates a unique dynamic. You often have less raw knowledge and expertise than the model, yet to get the best outcome you must be the one to guide it.
You are the Lead. You set the constraints. You own the code.
Below are the techniques I’ve found most effective and use frequently for leading the LLM while getting maximum value from the pairing:
100% Understanding is Non-Negotiable #
While this applies to any code, for LLM-generated code it is especially important: If you don’t understand it 100%, don’t commit it.
It is tempting to gloss over a complex regex or a clever one-liner that “just works” (or looks like it does). But if the model generates logic you couldn’t write yourself, make it explain it until you fully understand it. There is no shame in asking for explanations. The barrier of asking is low, since you’re not asking a colleague but a tool with infinite patience and zero judgement.
Example Prompts #
To Understand Logic (newly written or existing):
Explain `some_function_name` step-by-step. What happens if the input is empty?
Do I even need this much logic here? Couldn't this be solved with an if/else statement?
To Visualize Architecture:
Generate a Mermaid diagram showing the state transitions of `SomeClassName` in `some_file.py`. Highlight potential issues and especially race conditions.
Visualize the call graph of this endpoint for both, case A and case B.
Use these explanations and visualizations not just to learn, but to spot redundancies or potential issues you might have missed in the code.
Design & Exploration #
Especially if you don’t know exactly what you want to build and only have a high-level goal, use the LLM to finalize your technical requirements and design before writing code. Planning mode is ideal for this and available in most tools.
Give the model your rough constraints and let it ask you questions that help it refine your plan.
Once you have a good plan, use the model to challenge you and validate your implementation details. Ask “Why?” about specific choices or patterns to understand their trade-offs and “What If?” to explore architectural decisions.
Exploring these scenarios is crucial for understanding, not for expanding the scope of your current PR. If you find a great optimization that isn’t strictly necessary for the current task, note it down for a future refactor. At the same time, these questions can help you eliminate a lot of complexity from your current code while redesigning it.
Example Prompts #
Note: Especially for planning, the prompts should be much more detailed than the examples here. Add any relevant information and requirement and mention what you do not want and what constraints apply.
Clarify Requirements:
I need to build a rate-limiter for our API. We use Redis and FastAPI. Ask me clarifying questions about the traffic patterns, user tiers and failure modes to help me design the interface.
Improve Existing Implementation:
In `/my-existing-feature` I have an implementation of a trading system. Ask me clarifying questions about the business logic, edge cases and performance concerns to help me redesign it. My current implementation is not performant enough when handling high traffic.
Explore Trade-offs:
Why did you choose `pydantic` validators here instead of a simple `__init__` check? What are the trade-offs? Could I use dataclasses instead?
Explore Architecture:
What if we added a Redis cache layer here? How would that complicate the invalidation logic? Are there other options?
Code Review & Refactoring #
Before you ask the model to review your code, review it yourself (See also:
The Role of the Reviewer in the Age of LLM-Generated Code). You are the context and code owner. You know that user_id is a string in the legacy DB but an integer in the new service. The model does not.
Once you are satisfied, ask the model to review your code.
Example Prompts #
Bug Hunter:
Review this function for potential edge cases, specifically around handling null inputs or network timeouts. Don't worry about style or existing linting errors.
Will this code throw an exception if the input is empty?
If I call this multiple times from different threads, can this lead to a race condition?
Stylist:
Suggest a refactor to make this loop more idiomatic Python, but do not change the underlying logic.
Performance:
Is there a way to optimize this query to reduce database load? Isn't this overfetching?
df is a polars dataframe. Please make use of a vectorized operation instead of a loop.
Documentation & Testing With Intent #
Generating tests is one of the highest-value uses of an LLM, but auto-generated tests often only cover the “happy path.” You need to drive the initial testing strategy and steer the model to cover edge cases. You should also make use of interactive debugging with live data and system components to see the behavior of the model in a realistic environment.
Example Prompts #
Debug Aid:
Write a standalone Python script that imports this module and runs the `process_data` function with a mock payload derived from `my-sample-data.json`, so I can step through it with the debugger.
Edge Case Coverage:
Write unit tests for this function. Focus specifically on boundary conditions: empty lists, negative numbers, and extremely large inputs. There is no need to cover the case where the input is a string longer than 100 characters.
Interactive Documentation:
Create a Jupyter notebook that demonstrates the usage of this API class, including examples of how to handle common errors and annotate the code with comments that explain the different states of the system.
Challenge the Model #
This is the most critical skill for anyone using AI. Even recent models are too agreeable. They hallucinate confidence and will often happily agree with your bad ideas and applaud you for them.
Therefore it is important to challenge the model thoroughly and continuously question its assumptions.
If you are almost certain about a better solution, don’t just suggest it. Ask with a tone of disbelief. And if you see an antipattern or mistake, don’t just point it out by saying it’s bad. Ask for an explanation instead or sarcastically compliment the model and see if it can justify its choice.
Example Prompts #
Trust but Verify:
Are you sure this will work for datasets with more than 200 columns?
The Understated Superior Solution (Testing for Expertise):
I'm thinking of using a Strategy pattern here but it's probably overkill. Maybe we should just stick with the if-else statements?
The Inferior Trap (Testing for Agency):
Great use of a global variable here to share state between these modules. What made you come to this conclusion?
Conclusion #
By shifting from passive consumption to active pairing, you don’t just develop faster, you also learn a lot. You gain a deeper understanding of your tools, your architecture and the trade-offs in your code. The model provides the raw intelligence, but you provide the wisdom, the context and requirements and the intent.
The challenge is to resist the temptation to accept model output that is most of the time correct but not always the best possible solution. Inadequate workarounds and suboptimal solutions accumulate technical debt and make it hard to understand and maintain the codebase.
You own the code, the correctness and the quality. Never give up control or accept code you did not read and understand.