Why I use AI to write code slower (and better)
It seems like everyone is obsessed with speed at the moment. Scroll any engineering feed, and you will see people building a full-stack app in minutes using an LLM. Engineering leaders are rushing to give everyone an AI copilot to ship features twice as fast. I think this is optimizing for the wrong thing.
That kind of speed may look impressive in a demo, but most production systems do not fail because someone typed code too slowly. They fail at the boundaries like traffic spikes, database writes, retries, and bad payloads.
Kafka streams, Node.js services, RabbitMQ queues, and React frontends are not the kind of systems where "it works" is enough. The real question is how it behaves when there is no happy path. It has to stay stable, change and fail safely in controlled ways.
AI makes code easy to produce. And that is the risk. The bottleneck is rarely typing code; it is thinking. It is judgment.
That changed how I use LLMs. I use LLMs every day, not to write code faster, but to write code slower, deeper and better. First, I want to know what can break. The code comes later.
Writing a React component, adding an API layer, or a backend service is trivial now. The hard parts are still the same.
- Data ownership: what owns the data?
- Downstream degradation: what happens when a downstream service drops off the network?
- System load: How does the service behave under a traffic spike?
- Resilience: How are retries (and backoff) implemented?
- Mitigation: What breaks down in the system and how do we protect against it?
- Recovery: How do we recover a system from a failure?
Without anchoring the AI with these constraints, it will generate code that works in isolation but fails in production. I have seen systems pass every integration test and still collapse in production.
The most common mistake is treating an LLM like an advanced autocomplete.
Instead, I write out the architecture and the data models, and then I use the model to walk me through every possible failure mode. We debate network timeouts, boundaries and state transitions. By the time I actually ask the LLM to write a line of code, the system's design is finalized. The generation of the actual code becomes the easiest part of the process.
There is another problem: the model is a yes-man. LLMs are fundamentally trained to optimize for user approval and conversational flow. If you push back, the LLM will flip the position and apologize most of the time, even if the original answer was correct. If you leak your architecture early in the prompt, the AI will instantly agree with you and just go with the flow to justify your bad idea.
So, I just show the problem, forcing the AI to propose independent solutions first.
I used to prompt like this:
"Write me a Node.js script to connect to RabbitMQ, consume messages from an order queue, and save them to a database."
The AI will spit out a clean snippet that opens a connection, listens, and runs a DB insert. It may look perfect, but it is not proper code.
Now I start here:
I need to write a Node.js script to connect to RabbitMQ, consume messages from an order queue, and save them to a database. Do not write any code yet. Instead, lets walk through the system design first. Focus on:
- Connection pooling to PostgreSQL
- Network blips between the service and RabbitMQ connection
- Message acknowledgment strategies
- Handling malformed messages without blocking the entire queue.
Slowing down this process forces you to refine the design, error handling and database connection strategies before a line of code is written.
I use AI to write code every day. But I do not let it decide the shape of the systems. I use it to challenge the design before I commit to it. We need to spend more time planning and less time blindly generating. Let the AI do the typing, but make sure you are the one doing the thinking.