OTFotf
All posts
General

How I Use LLMs to Help Me Write Code

S
Simon WillisonAuthor
5 min
How I Use LLMs to Help Me Write Code

A tutorial by Simon Willison. Featured in the OTF curated resource library.

My Daily Workflow

I use LLMs for coding every working day. Not for everything — but for specific tasks where they provide genuine value. Here's my honest breakdown:

~40% of my coding time involves LLM assistance. The remaining 60% is reading code, thinking about architecture, discussing with teammates, and writing code that's faster to type than to describe.

My primary tools: Claude Code for complex tasks (refactoring, new features), Cursor's inline completions for boilerplate, and ChatGPT for quick questions. I switch between them based on the task, not loyalty to any tool.

The key insight after a year of daily use: LLMs are best when you know EXACTLY what you want and need help with the HOW. They're worst when you're exploring and don't yet know WHAT you want.

Where LLMs Excel

Boilerplate and Scaffolding

Setting up a new API endpoint with proper error handling, validation, types, and tests? LLMs do this in 30 seconds. The code is correct, follows conventions, and would have taken me 15-20 minutes to type manually.

Test Generation

The single highest-ROI use case. I write the implementation, then ask the LLM to 'write comprehensive tests for this function including edge cases.' The tests are thorough, catch edge cases I wouldn't think of, and follow my testing patterns.

Refactoring with Clear Rules

'Convert all class components to functional components with hooks. Preserve all behavior and types.' LLMs handle this mechanically and accurately. Large-scale refactoring that would take days takes hours.

Learning New APIs

'Show me how to use the Stripe Subscriptions API with TypeScript. Include error handling.' Better than reading documentation because the example is specific to my tech stack and use case.

Where LLMs Struggle

Novel Architecture Decisions

When I ask 'should I use microservices or a monolith for this project,' LLMs give generic advice. They lack the context of my team's skills, deployment constraints, and growth trajectory. Architecture requires judgment that LLMs can inform but not replace.

Subtle Performance Optimization

LLMs suggest obvious optimizations (memoization, lazy loading) but miss subtle ones (database query patterns, cache invalidation strategies, memory allocation patterns). Deep performance work requires understanding the specific runtime behavior.

Cross-System Debugging

When a bug spans multiple services, databases, and caches, LLMs can help investigate individual components but struggle to hold the full system state in context. I still need to be the one connecting the dots.

Creative Problem Solving

When the solution isn't a known pattern — when you need to invent a novel approach — LLMs tend to suggest variations of existing solutions. Real innovation still requires human creativity and domain insight.

Patterns That Produce the Best Results

Four patterns I've developed for getting consistently good output from LLMs.

1

Show, don't tell

Instead of describing your coding style, paste an existing example. 'Write a new API endpoint following this pattern: [paste existing endpoint].' The output matches your style perfectly because the LLM has a concrete reference.

2

Constrain the output

Don't ask for 'a good solution.' Ask for 'a solution using React Query for data fetching, Zod for validation, and our existing ErrorBoundary component.' Constraints produce better output than freedom.

3

Review like a PR

Treat LLM output as a junior developer's PR. Read every line. Question decisions. Test edge cases. The LLM is fast but not always correct. Your review is the quality gate.

4

Iterate, don't regenerate

If the output is 80% right, fix the 20% through conversation: 'Good, but change the error handling to use our custom AppError class.' Don't regenerate from scratch — you'll lose the good parts.

More resources

On this page