1. Neti Neti

You’re staring at a bug in a codebase you didn’t write. The stack trace is four levels deep in a library you’ve never heard of. The last person who touched this module left the company six months ago. You have no theory. You can’t form a hypothesis, because you don’t know enough about the system to guess intelligently.

What do you do?

“Neti, Neti” is Sanskrit for “not this, not this.” The phrase comes from the Brihadaranyaka Upanishad. Brahman, the ultimate reality in Advaita Vedanta, isn’t any particular thing, so you can’t describe it directly. The only way to point at it is to say what it isn’t. The Katha Upanishad takes that abstract method and gives it a story.

Nachiketa, still a boy, watched his father give away his possessions in a ritual sacrifice meant to earn heavenly reward. He saw what the gifts actually were: old cows, barren, too weak to give milk. He saw the emptiness behind the ritual. He asked his father three times, “To whom will you give me?” Angry, the father snapped, “I give you to Death.”

So Nachiketa goes. He waits at Death’s door for three nights without food while Yama, the god of death, is away. When Yama returns, he is uneasy to have left a guest waiting unhonored, and offers three boons in return, one for each night.

The first boon: let my father welcome me home, free from anger. Granted. The second: teach me the fire-sacrifice that leads to heaven. Yama teaches it, and, pleased with the boy, names the rite after him.

Then Nachiketa asks his third question. Not wealth, not power, not long life. “When a person dies, some say they still exist, others say they don’t. Taught by you, I want to know.”

Yama refuses. “Even the gods once debated this. It is subtle. Choose something else.” Nachiketa won’t move. “If the gods were uncertain, and you say it is hard, I’ll never find a teacher like you. No other boon equals this.”

So Yama tries a different way. He offers everything. Sons and grandsons who live a hundred years. Cattle, elephants, gold, horses. Kingdoms. Music and good company. Every desire a mortal could name. Only stop asking about death.

Nachiketa refuses again. “These things last until tomorrow, and they wear out the senses. Even a whole life is short. Keep your horses. Keep your dancing and singing. No one is made happy by wealth. Having seen you, what use are riches?”

Yama, out of offerings, finally teaches.

The shape of the story is the method. Each time Yama offers something, Nachiketa refuses, and each refusal narrows what’s left. What remains when everything else is gone is the truth he came for.

Conventional debugging is taught as hypothesis-driven search. You form a theory about what’s wrong. You test it. If you’re right, you fix the bug. If you’re wrong, you form a new theory.

This works when you know the system. Your theories have decent odds, because the architecture lives in your head: the data flow, the failure patterns, the places things usually break. You’re debugging inside your own mental model.

It fails when you don’t know the system. No mental model means no useful hypotheses. You guess, you guess wrong, you guess again, and each wrong guess costs time while the bug stays put. In an unfamiliar codebase, hypothesis-driven debugging isn’t a method, it’s gambling.

I keep relearning this. The first hour in a system I didn’t build is always the same: a theory, a test, a wrong answer, repeat. The theories aren’t the problem. The problem is trying to find the bug at all, when the move is to eliminate what isn’t the bug.

Linus Torvalds wrote the first version of Git in April 2005, and git bisect was there in the early days. The idea is binary search over history. The Linux kernel absorbs thousands of commits per release. When a regression lands, a driver that worked in one version and fails in the next, nobody reads the commits looking for the cause. You mark a known-good commit and a known-bad one. Git checks out the midpoint. You test, mark it good or bad, and Git halves the remaining range and checks out a new midpoint. After about thirteen steps you’re at the exact commit. That is log base two of ten thousand. Thirteen tests to certainty.

At no point did you search for the bug. You searched for the boundary between working and broken. The commit at that boundary is the bug, and you didn’t find it. You eliminated everything else until it was the only thing left.

Git’s own README, written in that first 2005 commit, calls it “the stupid content tracker.” Linus meant the word. The tool knows nothing about your code, and git bisect knows even less. It doesn’t know kernels, or drivers, or what your test proves. It halves a range and asks one question: good or bad? The tool that admits it knows nothing is the one that converges. The debugger who arrives with theories might not.

You can hand even the judgment to a script. git bisect run make checks out each midpoint, runs the build, and reads the exit code: zero is good, anything else is bad. Fourteen tests walk sixteen thousand commits down to the one that broke it, with no one watching. The method doesn’t need someone who understands the code. It needs a test that can say “not this.”

You don’t find the bug by searching for the bug. You find it by eliminating everything it’s not.

Remember Yama’s offerings: wealth, power, long life. Those are the easy debugging theories. “It’s probably a cache issue.” “Maybe the network is slow.” “Check whether the database is down.” Every experienced debugger knows them. They come to mind first because they’re obvious and easy to test, and in an unfamiliar system they’re almost always wrong.

Yama isn’t hiding the truth from Nachiketa. He’s testing whether the boy actually wants it. If wealth distracts you, you didn’t really want to know what happens after death. If the cache theory satisfies you, you didn’t really want to find the bug. The debugger who can say “not the cache, not the network, not the database” and keep narrowing is Nachiketa at the terminal. The easy theories aren’t obstacles. They’re a filter. Refuse them and continue.

The method works for deterministic failures. Regressions, bisectable bugs, anything where you can define “good” and “bad” without ambiguity and trust the test to tell the truth.

It doesn’t work for non-deterministic failures. A race condition that shows up one time in a thousand breaks it, because you can’t trust the test. You mark a commit “good,” but the bug was there and stayed silent. You mark another “bad,” but the failure was random. The signal is corrupted, and bisection converges on noise instead of the bug. Heisenbugs, failures that change behavior when you observe them, are worse. The test itself alters the outcome. You aren’t eliminating possibilities. You’re creating them.

Binary search assumes a stable reality, and so does the Upanishadic method. When reality isn’t stable, when the system is too emergent or too concurrent to give a straight answer, you need other tools. Every method has limits. This one works anywhere you can say, reliably, “this is not it.”

The Linux kernel is where this earns its keep. A release regresses, someone reports it, and a maintainer bisects between the last good release and the first bad one. Thousands of commits collapse to one in a dozen or so tests. The person running the bisect often has no idea what the offending commit does until Git hands it over. They didn’t theorize the cause. Elimination found it. This isn’t a trick that works once. It’s the standard way the kernel localizes regressions, documented in Git’s own manual and run every release cycle. git bisect run exists because the pattern is common enough to automate.

Nachiketa got his answer. Yama taught him the nature of the Self, the path beyond it, and what survives. That teaching fills the rest of the Katha Upanishad, and it’s a different essay.

This one is about the asking and the refusing. A boy stands at Death’s door, turns down every distraction on offer, and narrows the question until only the answer is left. The same shape runs under a command you already use. The next time you bisect a regression, you’re doing what he did. Not finding the answer. Refusing everything that isn’t it.

Further Reading

The Katha Upanishad is available in many translations. Eknath Easwaran’s (Nilgiri Press, 1987) is the most readable in English. Swami Gambhirananda’s translation with Shankara’s commentary (Advaita Ashrama) is the one to reach for when you want the traditional reading. Robert Ernest Hume’s public-domain translation (Oxford, 1921) collects the principal Upanishads, including the Brihadaranyaka passage where “Neti, Neti” first appears. Swami Sarvapriyananda’s lecture series on the Katha Upanishad (Vedanta Society of New York) walks through the Nachiketa and Yama dialogue in depth and is freely available.