AI pair programming for backend work: helps and fails

Where AI pair programming speeds up Node.js, TypeScript and PostgreSQL backend work, and where query plans, migrations and data decisions still need a human.

By Hamza Ghouri

Most of my production work is backend: Node.js, TypeScript, Express, PostgreSQL, Redis, and the AWS plumbing around them. AI pair programming has changed how fast I get through the boring parts of that work. It is also capable of very confident wrong answers. After six years of shipping backends, I have a fairly clear map of where an AI assistant earns its place and where I keep my hands on the wheel.

Where it genuinely helps

The pattern is simple: AI pair programming is strongest when the task is well specified and the result is easy to verify.

  • Boilerplate with a known shape. Express route handlers, Zod schemas from a JSON example, DTOs, repository methods that follow an existing pattern in the codebase. I describe the shape, point at a neighbour file, and review the output.
  • Translating between representations. SQL to an ORM query, a TypeScript type to a JSON Schema, a curl command to a fetch call. These are mechanical and easy to check.
  • First drafts of tests. It is fast at scaffolding cases. It is weak at choosing the right ones, which I come back to below.
  • Explaining unfamiliar code. Pointing the assistant at an old module and asking "what calls this and what does it assume?" is a quick way to get oriented, as long as I confirm the answer against the code.
  • Small refactors. Extracting a function, renaming across a module, converting callbacks to async/await.

In all of these, I can read the result and know within a minute whether it is right.

Where it fails on backend work

Backend bugs hide in places the model cannot see from the file it is editing.

Query performance

This is the big one. On Buffdudes, the fitness platform I built the backend for, the fix for a slow workout history endpoint was a composite index on user_workouts that took the query from around 4 seconds to around 200ms. I wrote about it in scaling a fitness platform to 60K users. Finding that fix required reading the actual query plan against real data volumes.

An AI assistant has no access to your table sizes, your data distribution, or what the planner actually does. Ask it to "optimise this query" and it will suggest plausible indexes, sometimes the right one, sometimes one that duplicates an existing index or will never be used because of column order. The only reliable way to know is to run it:

EXPLAIN (ANALYZE, BUFFERS)
SELECT id, workout_id, completed_at
FROM user_workouts
WHERE user_id = $1
ORDER BY completed_at DESC
LIMIT 50;

I use the assistant to help read a plan once I have it. I do not let it guess at one.

N+1 queries and ORM behaviour

Generated ORM code often looks clean and fires one query per row. The model writes what reads naturally, and lazy-loaded relations read naturally. I check the query log for anything that touches a list.

Migrations

A migration that adds a column with a default, rebuilds an index, or changes a type can lock a large table. The assistant does not know your table has millions of rows or that a mobile client still reads the old column. Every migration it drafts gets reviewed against production data size and deploy order.

Concurrency and background work

Race conditions, overlapping cron runs, retry storms, idempotency on webhook handlers. These depend on how the whole system behaves under load, which is invisible from a single file. I write these paths myself or review generated versions very slowly.

Invented APIs

Assistants confidently call methods that do not exist, import from paths that are not in the repo, or read env vars nobody defined. TypeScript catches some of it. The rest shows up at runtime. I grep for anything I do not recognise.

How I pair with it day to day

My working loop looks like this:

  1. Write the intent down first. A short spec or even a few bullet points: inputs, outputs, error cases, which existing module to follow. This is Spec-Driven Development at small scale, and it makes the assistant's output dramatically better.
  2. Point it at real examples. "Follow the pattern in this file" beats a paragraph of description.
  3. Keep the diff small. One endpoint, one function, one migration. Large generated diffs are where review quality drops.
  4. Verify by running. Hit the endpoint, run the test, check the query log, run EXPLAIN. Compiling is a low bar.
  5. Own the result. If it ships under my name, I can explain every line of it.

A rule of thumb

I sort backend tasks into two buckets. If being wrong is cheap and obvious (a failing type check, a test that breaks, an endpoint that returns 500 immediately), I let the assistant draft freely. If being wrong is expensive and quiet (a slow query that only hurts at scale, a migration that locks a table, a race that shows up once a week), the assistant is a sounding board and I make the call.

That split has held up across very different products, from REST APIs for mobile apps to the AI services I build now.

The short version

AI pair programming is a real speedup for backend boilerplate, translations, refactors and test scaffolding. It is unreliable on query performance, migrations, concurrency and anything that depends on production data. Give it a clear spec and a real example, keep diffs small, and verify by running the code. Query plans still need a human reading them.

Want this kind of work on your product?

Get in touch