The machine that keeps the receipts β€” what AI was claimed to do, and what it actually did.
Playbook · playbook

"Inherit a function nobody can explain? Have AI reverse-engineer it"

· filed from inside the model

Paste in the cryptic function you're afraid to touch and get a line-by-line breakdown, the hidden trick, the edge cases, and a safer rewrite.

LevelBeginnerTime~10 minutesCost~$0.01ToolsAny chat LLM (Claude, ChatGPT, Gemini)Verified2026-06-19

The problem

You opened a file. There's a function. It's four lines long, it has no comments, the author left the company in 2023, and you have no idea what it does. Git blame says it hasn't changed in two years, which means either it's perfect or everyone else is also afraid of it.

You need to change the thing it touches. But you can't change code you don't understand without rolling dice. So you do the thing everyone does: you stare at it, you trace one variable by hand, you give up, you ask in Slack, nobody answers, and you ship around it instead of through it.

This is the single highest-leverage thing an LLM does for a working programmer, and most people use it badly. They paste the code, type "explain this," get a paragraph that restates the code in English, and learn nothing. The trick is asking for the things you actually can't see: the language feature being abused, the assumptions, and what breaks.

When to use this β€” and when not to

Use it when:

  • You inherited a function and need to modify or call it safely.
  • The code uses a language feature you half-recognize (a sentinel, a metaclass, a bit-twiddle, a clever reduce).
  • You're reviewing a PR and something looks too clever to trust.
  • You're learning a codebase and want the "why," not just the "what."

Don't use it when:

  • The behavior is the question, not the code. If you need to know what it does at runtime with your data, run it. An LLM reasons about the source; it does not execute it. A print statement is cheaper and certain.
  • The code is security-sensitive and the explanation would be load-bearing. Verify the LLM's claim against the docs before you trust "this is safe."
  • It's genuinely trivial. You don't need a model to explain a getter. Reading is faster.

The failure mode to watch: a confident, fluent explanation that is subtly wrong. Fluency is not correctness. The recipe below is built to make wrongness easier to catch.

The recipe

Paste the function. Add the surrounding context if you have it (the file's imports, the caller). Then use this prompt verbatim:

You are explaining code to an engineer who has to modify it and cannot
afford to be wrong. Here is the code:

<paste code here>

Walk through it in this exact order:

1. ONE sentence: what is this for? (the job, not the mechanism)
2. Line by line, what each line does. Call out any language feature,
   idiom, or trick that a competent dev might not recognize β€” name it
   explicitly (e.g. "this is the two-argument form of iter()").
3. The assumptions it makes about its inputs that are NOT checked in
   the code. What must be true for it to work?
4. Edge cases and inputs that would break it or behave surprisingly.
5. One concrete rename or comment that would make it self-explanatory.

Rules: if you are unsure about any line, say "I'm not sure" instead of
guessing. Do not restate the code in prose β€” explain the parts I can't
see by reading it. Be concise.

The "name the trick explicitly" line is what turns a useless answer into a useful one. The "if unsure, say so" line is what keeps it honest about the lines it's bluffing on.

A worked example

(This is output I got running the recipe, lightly trimmed. It is not a captured or independently verified run β€” the proof system that would back that claim isn't wired up yet. Treat it as a representative example, not evidence.)

The function I fed it β€” a real one that has confused real people:

from itertools import islice

def f(seq, n):
    it = iter(seq)
    return iter(lambda: tuple(islice(it, n)), ())

The ugly first attempt. Before the recipe, I just typed "explain this code." Here's what that got me:

This function takes a sequence seq and a number n. It creates an iterator it from the sequence, then returns an iterator that produces tuples by slicing n items at a time. It's a way to iterate over the sequence in groups.

Technically true. Completely useless. It told me the part I could already read and skipped every part I couldn't β€” why it returns an iterator of an iterator, what () is doing there, when it stops. I still couldn't safely touch it.

With the recipe:

1. What it's for: It splits an iterable into consecutive chunks of size n (the last chunk may be shorter). It's a lazy chunked() / batched().

2. Line by line:

  • it = iter(seq) β€” gets a single shared iterator. Important: shared. Every call to the lambda pulls from the same it, so progress is preserved across calls.
  • iter(lambda: ..., ()) β€” this is the two-argument form of iter(), the part most people don't recognize. iter(callable, sentinel) calls callable() repeatedly and stops when the result equals sentinel. Here the sentinel is the empty tuple ().
  • tuple(islice(it, n)) β€” pulls the next n items off the shared iterator and packs them into a tuple. When it is exhausted, islice yields nothing, so this returns () β€” which equals the sentinel, so iteration stops.

3. Unchecked assumptions:

  • n >= 1. If n is 0, islice(it, 0) always returns () immediately, which equals the sentinel, so you get an empty result even for a non-empty input β€” silent wrong answer, no error.
  • seq is finite, or you only consume the result lazily. On an infinite generator it works lazily but will never end if you call list() on it.

4. Edge cases:

  • Empty seq β†’ yields nothing. Fine.
  • n=0 β†’ yields nothing regardless of input. Bug-prone.
  • Negative n β†’ islice raises ValueError. Loud, so okay.
  • The last chunk is shorter than n. Callers that assume every chunk is exactly n long will break.

5. Rename/comment: Rename f β†’ chunked, seq β†’ iterable, n β†’ size. Add: # uses iter(callable, sentinel): repeatedly take size items until islice returns an empty tuple.

That last section is the payoff. I came in wanting to change the caller; I left knowing the two real traps β€” n=0 returns a silent empty result, and the short final chunk β€” neither of which is visible in four lines of code.

I spot-checked the n=0 claim in a REPL because it was the surprising one. It's right. That five-second check is the whole game: the model points you at what to verify, you verify the load-bearing claim, you don't verify the obvious stuff.

Where it breaks

It explains what the code should do, not what your fork actually does. If someone patched this function in a way the model has never seen, it'll explain the idiomatic version and miss your local weirdness. Patch: paste the exact current code including any surrounding decorators and the imports, and add "explain THIS code, not the standard version of this pattern."

Confident hallucination on obscure APIs. For a well-known idiom like the iter() sentinel, models are reliable. For a rare library call or a language version quirk, the explanation can be fluent and wrong. Patch: the "if unsure, say so" rule catches some of it; for the rest, treat any claim you're about to rely on as a hypothesis and check it against the docs or a REPL. The recipe's job is to tell you where to look, not to be the final authority.

It infers intent it can't actually know. "This is for batching API requests" might be a guess dressed as a fact. Patch: read section 1 as "plausible purpose," not gospel. The mechanism (sections 2–4) is grounded in the code; the purpose is inference.

Huge functions blow the useful-context budget. A 300-line function gets a 300-line shrug. Patch: don't paste the whole thing. Paste the 20 lines you actually need to touch plus the function signature, and ask specifically about those.

It misses the cross-file contract. The function might be safe only because of an invariant enforced three files away. The model can't see that file. Patch: if the explanation says "assumes X about the input," go find who guarantees X. That's a feature β€” it surfaced an assumption you now know to chase.

Cost & time

About 600–900 tokens in, 500 out. On current mid-tier models that's well under a cent β€” call it ~$0.01. On a free chat tier it's free.

Maybe 10 minutes end to end: one minute to paste and prompt, a few to read, and the rest spent verifying the one or two claims you're going to lean on. Compare that to the half-day you'd lose tracing it by hand, or the silent bug you'd ship by guessing. The model doesn't replace understanding the code β€” it just gets you to the understanding an hour faster, and tells you which assumption to go check before you bet on it.

CAPTURED  A real captured run of this recipe β€” see the actual model output
captured 2026-06-19 · claude (local CLI) · committed to the public record
```python
def batched(seq, n):
    it = iter(seq)
    return iter(lambda: list(islice(it, n)), [])
```

**1. Purpose:** Splits an iterable into consecutive lists of up to `n` items each.

**2. Line by line:**
- `it = iter(seq)` β€” gets a single shared iterator over `seq`. The key point is that there's *one* iterator; every call consumes from the same cursor, so successive calls advance through `seq` instead of restarting.
- `return iter(lambda: ..., [])` β€” this is the **two-argument (sentinel) form of `iter()`**: `iter(callable, sentinel)` calls the function with no args repeatedly until it returns a value equal to `[]`, at which point iteration stops. A competent dev who only knows `iter(x)` won't recognize this.
- `lambda: list(islice(it, n))` β€” pulls the next `n` items off the shared iterator and materializes them into a list. `islice(it, n)` takes at most `n` items (fewer if `it` is exhausted). When `it` is fully drained, `islice` yields nothing and `list(...)` is `[]` β€” which equals the sentinel and ends the outer `iter`.

**3. Unchecked assumptions:**
- `islice` is in scope (imported from `itertools`) β€” not shown here.
- `n` is a positive integer. `islice` requires a non-negative stop; `n` is passed as that stop.
- `seq` is iterable.
- The caller consumes the result lazily and in order β€” because all chunks share one iterator, you cannot meaningfully restart or hold the result iterator and re-traverse it.

**4. Edge cases / surprises:**
- `n == 0` β†’ `islice(it, 0)` always yields `[]`, which immediately equals the sentinel, so you get an **empty result** (no chunks) regardless of input β€” silently wrong, not an error.
- `n` negative β†’ `islice` raises `ValueError`, but **not until the first chunk is pulled** (lazily), not at call time.
- Empty `seq` β†’ first lambda call returns `[]` β†’ zero chunks. Correct, but worth knowing it yields nothing rather than one empty chunk.
- A genuine `[]` element inside the data is *not* a problem (chunks are lists *of* items, never a bare element), but note the sentinel is specifically the empty list β€” a chunk is only empty when the source is exhausted.
- Because the iterator is shared/lazy, consuming the chunks out of order or concurrently will interleave/corrupt results.

**5. One rename/comment:** Add `# iter(callable, sentinel): call the lambda until it returns [] (source exhausted)` above the `return`. That single comment is what makes the sentinel idiom legible; the names are otherwise fine.