Rubber Duck Debugging: Say Your Assumptions Out Loud
By Sergey Nosov
16 October 2026
Imagine a function that should take 20 percent off a $50 price. It returns −950. You read it, and every line looks right. You read it again, and it still looks right. Then you explain it out loud, one line at a time, to the rubber duck on your desk. When you reach the line that computes the discount, you hear yourself say that the percent is 20, and you hear the bug.
That is rubber duck debugging: explaining your problem, line by line and out loud, to an object that cannot understand a word of it. The duck does not need to understand anything. You do. Saying the problem aloud, in order, can bring out assumptions that another silent reread would leave unexamined. The technique is old and nearly free. The bugs it catches live in the gap between what you meant and what the code says.
This article starts with two books from 1999, walks the opening bug through a full explanation and a failing test, and looks at the research on why explaining works. Then come a short method, other listeners for when no duck is handy, and the AI tools that have borrowed the name.
Where the Duck Comes From
The name comes from The Pragmatic Programmer, by Andrew Hunt and David Thomas, first published in 1999. Its section on debugging includes a passage headed “Rubber Ducking”:
A very simple but particularly useful technique for finding the cause of a problem is simply to explain it to someone else. The other person should look over your shoulder at the screen, and nod his or her head constantly (like a rubber duck bobbing up and down in a bathtub). They do not need to say a word; the simple act of explaining, step by step, what the code is supposed to do often causes the problem to leap off the screen and announce itself.
In the passage, the duck is a simile for a nodding listener; a real one appears in a footnote. When Thomas was an undergraduate at Imperial College in London, he worked with a research assistant named Greg Pugh. For several months, Pugh “carried around a small yellow rubber duck, which he’d place on his terminal while coding.”
The 20th Anniversary Edition, published in 2019, keeps the passage and adds a fallback: “And if you don’t have a person, a rubber duck, or teddy bear, or potted plant will do.”
The duck had company. Also in 1999, Brian Kernighan and Rob Pike made the same point in The Practice of Programming, under the heading “Explain your code to someone else.” Their reason fits in one sentence: “This will often cause you to explain the bug to yourself.” Kernighan hosts an excerpt of the debugging chapter that tells the story of another silent listener:
One university computer center kept a teddy bear near the help desk. Students with mysterious bugs were required to explain them to the bear before they could speak to a human counselor.
Walking the Duck through a Bug
Here is the function from the opening, in JavaScript, kept tiny so that the whole walk fits on the page:
function applyDiscount(price, percent) {
const discount = price * percent;
return price - discount;
}
// Expected 40. Prints: -950
console.log(applyDiscount(50, 20));
Read silently, it looks fine. The names are sensible, the arithmetic is one multiplication and one subtraction, and nothing is misspelled. Now explain it to the duck, starting with what it should do. Out loud, it might go like this:
This function takes a price and a percent and returns the discounted price. For a price of 50 and 20 percent off, it should return 40, but it returns −950. First line: the discount is the price times the percent, so 50 times… 20. Twenty, not 0.20.
That is the moment the bug surfaces: the formula needed to divide by 100. As written, the discount comes out as 1,000 instead of 10, and 50 minus 1,000 is −950. You did not find the bug with a breakpoint or a log statement. You found it by saying it.
On the page, price * percent looks exactly right, because in your head 20 percent already means 0.20. Explaining made you say the value and its meaning in one breath, and the two did not agree.
Making the Fix Stick
Write the test before the fix, and watch it fail. The 2019 edition of The Pragmatic Programmer makes that a tip: “Failing Test Before Fixing Code.” Here is the fix with its test:
import assert from "node:assert/strict";
// percent is 0-100, not a fraction
function applyDiscount(price, percent) {
const rate = percent / 100;
const discount = price * rate;
return price - discount;
}
// 20 percent off 50 is 40
assert.equal(applyDiscount(50, 20), 40);
// Boundaries: 0 and 100 percent
assert.equal(applyDiscount(50, 0), 50);
assert.equal(applyDiscount(50, 100), 0);
Run against the original function, the first assertion fails and reports −950 where it expected 40. I ran both samples on Node.js 24. Production pricing code would also need exact money arithmetic, such as whole cents, and a rounding rule; this example’s numbers happen to come out exact.
Another assumption lives outside the function. The code now expects a percent such as 20. Nothing stops a caller who thinks in fractions from passing 0.2 and getting 49.9: a plausible price, and a wrong one. The comment in the fix states the unit, but a range check would not catch that call, because 0.2 is also a valid percent. Explain each call site to the duck too, with its values and what they mean.
Why Explaining Works
Hunt and Thomas gave the short answer in 1999: “In explaining the problem to another person you must explicitly state things that you may take for granted when going through the code yourself. By having to verbalize some of these assumptions, you may suddenly gain new insight into the problem.”
Psychologists have measured a related gap, between how well people think they understand something and how well they can explain it. In a 2002 paper, Leonid Rozenblit and Frank Keil of Yale University named the illusion of explanatory depth. “People feel they understand complex phenomena with far greater precision, coherence, and depth than they really do,” they wrote.
In their first study, sixteen Yale graduate students rated how well they understood everyday devices, such as a zipper, a flush toilet, and a cylinder lock. Then each wrote “a detailed, step-by-step causal explanation” of four of them and rated their understanding again. The ratings fell.
The authors trace the illusion partly to “a confusion between what is represented in the head with what can be recovered from a display in real time.” By analogy, a formula on your screen can feel understood before you have checked what its inputs mean.
Explaining can also build understanding, not only expose the lack of it. In a 1994 study, Michelene Chi and her colleagues at the University of Pittsburgh gave eighth-grade students a textbook passage on the human circulatory system. Fourteen of them read each sentence out loud and explained it to an experimenter, who asked follow-up questions. Ten others read the same passage twice. The students asked to explain gained more from pretest to posttest, though they also spent nearly twice as long with the passage.
Neither study involved code, and a duck asks no follow-up questions. Programmers report that the duck works; the studies suggest why it might.
How to Talk to a Duck
The method comes down to five rules:
- State the contract first. Say what the code should do, with a concrete input and the result you expect, and then what it actually does. The gap between the two results is what you are hunting.
- Go line by line, in the order the code runs, for one failing case. Do not wave a loop through with a one-line summary; walk one pass and say how the loop starts and ends. A skipped line is where an assumption hides.
- Say the values, what they mean, and why each step is right. Reading a line aloud is not explaining it. Do not say “multiply by the percent.” Say “multiply 50 by 20, and 20 is a percent, not a fraction.” Units, types, and ranges belong in the sentence; the opening bug hid in a unit.
- Do not skip the line you trust. In the 2019 edition, Hunt and Thomas even use a discount as their example: “In that discount calculation algorithm—the one you knew was bulletproof and couldn’t possibly be the cause of this bug—did you test all the boundary conditions?”
- Treat every “should be” as an assumption. When you hear yourself say that a value should be positive or a list should be sorted, stop and check it with a log line or a debugger. As their tip puts it, “Don’t Assume It—Prove It.”
If you reach the end and nothing surprised you, the bug may be in something you did not explain, such as the input, the configuration, or a library. Narrow the case, as Kernighan and Pike advise, to “the smallest input where the bug still shows up.” The bug may also hide in a belief you stated without doubting it. A colleague can challenge that belief; a duck cannot.
No Duck Required
The duck is a prop; the explanation does the work. Other listeners help too:
- A colleague. Kernighan and Pike note that it sometimes takes only a few sentences before the explainer says, embarrassed, “Never mind, I see what’s wrong. Sorry to bother you.” They add that “you can even use non-programmers as listeners.”
- A notebook. In the 2019 edition, Hunt and Thomas recommend keeping a paper engineering daybook, which “acts as a kind of rubber duck.” In their words, “When you stop to write something down, your brain may switch gears, almost as if talking to someone.”
- A message you may never send. Jeff Atwood, a cofounder of Stack Overflow, wrote in 2012 about the many people who solved their own problem “in the process of writing up their thorough, detailed question.” His advice: “Forcing yourself to fully explain your problem—ideally in writing—will frequently lead to new insights and discoveries.”
Whatever the listener, commit to the whole explanation, as if to someone who has never seen your code. Atwood calls that commitment “the critical part” and insists on it “even if you end up throwing the question away.” A message that shows you the bug halfway through has done its job; delete it.
A Duck That Talks Back
AI tools have since borrowed the name. Harvard’s CS50 course tested a chatbot “implemented as a virtual rubber duck” in 2023. On 6 April 2026, GitHub introduced Rubber Duck in Copilot CLI as an experimental agent. It uses “a second model from a different AI family” to review the coding agent’s plans and work.
Visual Studio Code 1.135, released on 26 August 2026, added an experimental /rubber-duck command for Copilot agent host sessions. According to its release notes, it lets you “get a second opinion from a complementary model on the agent’s work.” GitHub’s documentation ties the name to the old technique: before a nontrivial change, “Copilot can stop, articulate its current thinking, and have it scrutinized by an independent reviewer.” “Unlike a real rubber duck,” the page adds, “this one talks back.”
GitHub reports cases where the reviewer caught real problems. But the classic duck adds no ideas of its own, so the insight comes from your own explanation. When you take a bug to an AI chat, write that explanation first. Often the bug surfaces before you send it; if not, you send a better question. Leave out secrets and personal data, and check the answer: the CS50 team warns that AI “tends to exhibit a tone of complete and authoritative confidence even when wrong.”
Takeaways
- Start with the contract: the input, the result you expect, and the result you got.
- Walk one failing case line by line, in the order the code runs, and say each value with its meaning and unit.
- Give the line you trust the same attention as the rest, and treat every “should be” as an assumption to prove as you go.
- When explaining finds nothing, shrink the failing case.
- Write a failing test for the bug before you fix it, and explain the call sites too.
- No duck? Explain to a colleague, a notebook, or a message you may never send.
- Before you ask an AI assistant, write the explanation anyway, and check its answer.
Teaching forces clarity, and clarity finds bugs. The duck simply gives you someone to teach.
Further Reading
- The Pragmatic Programmer: Your Journey to Mastery, 20th Anniversary Edition (David Thomas and Andrew Hunt, Addison-Wesley, 2019)
- From Chapter 5, Debugging, of The Practice of Programming (Brian W. Kernighan and Rob Pike, Addison-Wesley, 1999)
- The Misunderstood Limits of Folk Science: An Illusion of Explanatory Depth (Leonid Rozenblit and Frank Keil, Cognitive Science, 2002)
- Eliciting Self-Explanations Improves Understanding (Michelene T. H. Chi, Nicholas de Leeuw, Mei-Hung Chiu, and Christian LaVancher, Cognitive Science, 1994)
- Rubber Duck Problem Solving (Jeff Atwood, 13 March 2012)