Use AI to understand code

By

The best use of AI is not generating more code. Ask focused questions, attack a change from every angle, and keep the mental model. Code is the byproduct.

~~~

Most people open an AI chat and ask it to write something.

I do the opposite more often. I point it at code that already exists and I ask questions until I understand it.

The files that come out later are useful. The understanding is the thing I keep.

Writing is the easy part now

I ship a lot of code with coding agents. Cursor, Claude, Codex. They are fast. They are also happy to produce a diff I have not read.

I felt this years ago already. The generated code did not feel mine. I had to go back and read it after the fact.

That extra reading is not optional if you want to maintain the project next month.

So I changed the default. I use AI to build a mental model first. Then I let it write. Then I attack the result with more questions.

Give it the repo

A pasted snippet is not enough.

If I ask “does this leak data?” and the model only sees one function, it will answer about that function. The leak might be three files away. The permission check might live in a helper nobody calls.

I give the agent the project. Read access is the point. I want it to follow the call, the query, the email, the cookie.

I also keep project facts in an AGENTS.md file so it does not reinvent how the site builds.

A model that cannot see the system cannot give you a map of the system.

Ask one sharp question

A vague prompt gets a vague tour.

“Explain this file” produces a summary. A summary is not understanding. It is a rewrite in English.

I ask one thing.

What happens when this webhook arrives twice?

Where does this email go after we store it?

If this function returns 200, did the user get access?

Does the error reach the person who paid, or only my logs?

Each answer is a brick. I stack them. After five or six, I can see the system.

The model is good at this. It can jump from a function to HTTP to a database to a user-facing error without getting tired. I cannot hold all of that in my head on the first pass. I can hold it after I asked.

Build the map in layers

I do not ask for the whole architecture at once. I move through four layers.

First, I ask about the entry point:

What starts this flow, and what input crosses the boundary?
Name the route, event, command, or component.

Then I follow the data:

Trace `orderId` from the request to storage.
List every function that transforms or validates it.

Then I follow side effects:

Which operations leave this process?
Include database writes, network calls, emails, and queue messages.

Finally, I ask about failure:

For each side effect, what happens if it succeeds and the next step fails?

The order matters. Entry point, data, side effects, failure. It turns a pile of files into a path I can inspect.

I ask the agent to cite file paths and function names. A claim without a location is harder to verify and easier to invent.

Ask for evidence, not confidence

Models use confident language for both facts and guesses.

I make the output separate them:

Answer in three parts:

1. Confirmed behavior, with file and function references
2. Assumptions you could not verify
3. The smallest commands or tests that would resolve those assumptions

This does not make the answer true. It makes uncertainty visible.

Suppose the agent says a webhook is deduplicated. I ask where the durable key is stored. If it points at an in-memory Set, the behavior is only local to one process. If it points at a unique database constraint, I can inspect the migration and the insert.

The second answer is useful because it can be checked.

A real walkthrough

Let’s take a purchase webhook. I have one on this site. Paddle hits /purchase. We grant course access and send an email.

Here is a simplified version of the decision in the middle:

if (body.alert_name) {
  return Response.json({
    message: `Ignoring account alert: ${body.alert_name}`,
  })
}

const productId = body.p_product_id

If I drop this on an agent and say “what is this doing?”, I get a paragraph. Fine. Not enough.

I ask the next questions myself.

Why are there two payloads for one purchase?

What happens if we handle both?

The in-memory Set that dedups emails, does it survive across Cloudflare isolates?

If the newsletter subscribe fails, do we still write the access record?

If the access record fails, did the buyer already get the email?

That last one matters. The buyer thinks they are in. My retrieval page might not list the course yet.

None of those answers live in one function. They live in the path.

This is the job. Not “write the webhook”. Understand the webhook.

Attack a change

When an agent opens a change, I do not ask “does this look good?”

I ask it to attack the change.

What existing behavior does this touch?

Can we fold this into a function we already have?

Does a failure show up in the UI, or does it fail silent?

Is this query doing more work than it needs?

Then I read the answers with suspicion. The model is eager. It will invent a risk if you ask for risks. It will also miss a real one.

My job is to keep the ones that match the code.

I do this in a fresh chat when I can. The session that wrote the code is biased toward defending it. A new session only sees the diff and the repo.

Plan before you generate

Same habit, earlier.

Before I let an agent add a feature, I ask what already exists.

Is there a helper for this?

Which pages call this function today?

If we change the return shape, who breaks?

I also ask it to surface choices. KV or D1. Cookie or server session. One endpoint or two. I want the options on the table. I pick.

If I skip this, the model picks for me. Then I inherit a design I never agreed to.

A short written brief helps. Put the facts and the constraints in it. Make the agent show you its assumptions before it starts. The point is simple: decide first, generate second.

Tests are another way to ask

“Write tests” is a weak prompt.

“Write a test that proves a duplicate webhook does not send a second email” is a question in disguise.

If the test is hard to write, I do not understand the behavior yet. That is useful. The gap is the lesson.

I still read the tests. A model can prove the wrong thing with great confidence.

Turn the model into a debugger

When code fails, I give the agent evidence in the order the program produced it.

I include:

  • the command I ran
  • the complete error, not one line
  • the expected behavior
  • the smallest input that reproduces it
  • any recent change that might be related

Then I ask for competing explanations:

Give me the three most likely causes.
For each cause, name one observation that would prove or disprove it.
Do not change files yet.

This is better than “fix the bug”. It keeps diagnosis separate from implementation.

If two explanations predict the same observation, the test is weak. I ask for a check that separates them.

For example, a failed API call might come from invalid input, an expired credential, or a network failure. Logging the status code separates the first two from the third. Inspecting the response body and credential expiry separates the first from the second.

The model helps design the experiment. The program gives the answer.

Read the diff as a story

A diff tells me what changed. It does not tell me whether the new behavior is complete.

I ask the agent to walk the diff in execution order:

Trace one successful request through this diff.
Then trace one invalid request and one dependency failure.
Point out any branch that has no test.

This catches a common problem: the happy path changed in one file, but error mapping, cleanup, or the caller stayed on the old contract.

I also ask what did not change. If a return type changed but no callers changed, either the change is backwards compatible or we missed something. I want to know which.

Compare the explanation with runtime behavior

Static reading has limits.

Dynamic configuration, framework routing, generated code, and environment variables can change what runs. The agent may trace the obvious function while production calls another one.

I verify the map with small observations:

  • run the focused test
  • call the endpoint with a known input
  • inspect the response status and headers
  • add a temporary breakpoint
  • check which handler appears in the stack trace
  • inspect a database row before and after

The HTTP course helps here because requests and responses become evidence instead of magic. For general code reading, the free JavaScript course builds the vocabulary the model assumes you have.

The best workflow alternates between explanation and observation. Ask, inspect, run, compare.

Do not outsource the judgment

People ask the model if permissions are enforced downstream, then they relax.

Do not relax.

The model can trace a call. It cannot be the authority on whether your product is safe. You still open the other file. You still try the request. You still check the header.

I wrote about hardening public form endpoints for this reason. Body size limits, allowlists, rate limits. Those are checks you verify, not vibes you accept.

Same for “is there a leak?”. Ask. Then search. Then look at the logs. An email in console.log is a leak if those logs leave your machine.

The HTTP course is worth it here. Status codes, headers, cookies. You need that vocabulary to notice when the model’s story does not match the response.

This does not teach you how to read

If you are learning, there is a trap.

You can collect answers and feel smart. You did not practice reading. The next file, without a chat open, is still opaque.

My advice: try first. Read the function. Guess. Then ask. Compare.

You need a feel for when the answer is nonsense. That feel comes from doing the work yourself a few times.

I teach JavaScript and Git for this. The fundamentals are how you notice the model is wrong.

Common mistakes

Asking for a file summary

Summaries compress syntax. They rarely explain the behavior that crosses files.

Ask about one path, value, or failure instead.

Accepting a list of possible risks

If you ask “what could go wrong?”, the model can invent a long list that fits any application.

Ask which risks are reachable in this code. Require the route from input to consequence.

Letting the writing session review itself

The session that produced a design already has a story about why it works. A fresh review starts with the repository and the diff.

Changing code before the model is clear

An agent that cannot explain the current path should not rewrite it yet. Stop and narrow the question.

Treating generated tests as independent evidence

A test written from the same mistaken assumption can confirm the mistake. Compare it with the requirement and force at least one failure case.

Pasting secrets into context

The agent rarely needs real credentials or customer data to explain a path. Use redacted payloads and local fixtures.

How I use this every day

I run a bunch of small sites. I am usually the only person in the repo.

A typical session looks like this.

I open the project in Cursor. I point at a function I have not touched in months. I ask what it does, then I ask what it does not do. I ask who calls it. I ask what happens when it fails.

If I am about to change it, I ask what else will move. Then I write a short plan, or I let the agent write one and I cut it.

Then I let it implement one slice. I review. I attack. I keep what is real.

I do not do this for a one-off script I will delete tonight. Understanding has a cost. Spend it on code you will live with.

It is also a poor fit when you already know the path cold. If I wrote the function this morning, I do not need a tour. I need a second pair of eyes on the one thing I might have missed.

Code is the byproduct

I still want the feature shipped.

I just do not want a repo I cannot explain.

The output I care about is the model in my head. The files on disk are how that model shows up.

Ask focused questions. Stack the answers. Keep the understanding.

The code will follow.

Tagged: AI · All topics

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about ai: