Developer Debugging Guides2026-06-04·13 min read

Rubber Duck Debugging: Does It Actually Work?

Rubber duck debugging is a real, research-backed technique. Learn how to do it, when it works, and when you need a human senior dev instead of a duck.

#debugging#troubleshooting#programming#error-fix

FlowQL Team

AI Search Optimization Experts

You're 45 minutes into a debug session. The error message makes no sense. You've read the stack trace four times. You've Googled the error string. You've asked the AI assistant the same question in three different ways and it keeps giving you the same wrong answer. You're stuck.

Then your coworker walks over and says "what's happening?" You open your mouth to explain — and before you finish the first sentence, you already know what the bug is.

This is rubber duck debugging. And yes, it actually works.

What Is Rubber Duck Debugging?

Rubber duck debugging is a technique where you explain your code, line by line, to an inanimate object. The canonical prop is a rubber duck, though any object works. The act of articulating what your code should do — out loud, in plain language — forces a mental shift that frequently reveals the root cause of a bug on its own.

The term comes from a story in The Pragmatic Programmer by Andrew Hunt and David Thomas. A programmer would carry a rubber duck and explain code to it before asking colleagues for help. The duck became shorthand for "explain it before you escalate it." The book was published in 1999, but the underlying technique is older — developers have used variations of it since at least the 1970s.

The core mechanism: when you explain code verbally, you're forced to construct a precise, sequential model of what the code does at each step. This is cognitively different from reading code, where your brain pattern-matches familiar constructs and skips over them. Explanation slows you down and forces you to validate each assumption.

The Psychology Behind It (Why Explaining Helps)

Why does talking to a rubber duck — an object that gives no feedback at all — actually work?

The research points to a phenomenon called the self-explanation effect, documented in learning science since the 1980s. Chi et al. (1994) showed that students who explained material to themselves while studying learned significantly more than students who simply re-read it. The act of generating an explanation — even silently — forces the brain to construct a coherent model rather than passively consuming information.

When you're debugging, you're usually running in execution mode: stepping through the code mentally, checking values, looking for the divergence point. This mode is fast and shallow. Explanation mode is different — it requires you to state assumptions, not just check them. "The function receives an array, iterates over each element, and returns the total" is a claim. And as soon as you make a claim, your brain starts checking it.

There's also a second mechanism: the curse of knowledge. When you've written code, you know what you meant it to do. This bleeds into how you read it — you see your intention, not the literal behavior. Explaining code to an audience (even an imaginary one) forces you to separate intent from behavior. You can't say "it loops over the items" when it's actually looping over the wrong variable — the duck doesn't share your mental context.

A third mechanism is reduced cognitive load from externalizing. Narrating what you're doing frees up working memory from having to hold multiple states simultaneously. You're offloading the "what have I checked so far" tracking to your voice, which leaves more mental capacity to notice anomalies.

Finally, there's the interruption of fixation. After staring at the same code for 20 minutes, you've entered a state of perceptual narrowing — you're focused on the areas you already suspect and you're not seeing the rest of the function. Starting fresh with a verbal explanation forces you to re-read from the top, which is often where the bug actually is.

How to Do Rubber Duck Debugging (Step by Step)

The rubber duck method is not complicated, but most people do it wrong — they explain the symptoms instead of the code.

1. Get the duck (or equivalent). Any object works. Some developers use a second monitor with a blank document open. Others type their explanation out loud as a comment. The object itself is not what matters — the act of narrating is.

2. State the goal first. Before touching the code, say out loud what the function or component is supposed to do. "This function takes a list of transactions, filters the ones marked as pending, and returns the total dollar amount." Stating the goal creates a reference point for the explanation.

3. Walk through the code line by line. Not the logic in your head — the actual code. Say what each line does, literally. "This line declares a variable called total and sets it to zero. This line calls Array.filter on transactions..." Read it as if you've never seen it before.

4. State your assumptions out loud. When you reach an assumption, name it explicitly. "I'm assuming item.amount is always a number here." Assumptions are where bugs hide. If you can't state the assumption confidently, that's your bug.

5. When you find the discrepancy, stop. You'll often notice mid-sentence that a word you're about to say doesn't match what the code actually does. Trust that moment. Don't talk yourself out of it.

6. If you finish the walkthrough and find nothing, repeat from the caller's perspective. What is the function receiving as input? Walk through how the calling code assembles that input.

Rubber Duck Debugging in Practice: Code Examples

Here are four common scenarios where the rubber duck technique catches bugs that staring at the code doesn't.

Example 1: Off-by-one error in a loop

// Goal: sum every other element in an array (elements at index 1, 3, 5...)
function sumAlternating(arr) {
  let total = 0;
  for (let i = 0; i < arr.length; i += 2) {
    total += arr[i]; // Duck: "I'm adding arr[i] where i starts at 0 and increments by 2"
    // Duck question: "So I'm summing indices 0, 2, 4... but I said I wanted 1, 3, 5?"
    // Bug found: loop should start at i = 1, not i = 0
  }
  return total;
}

The fix is one character. But reading the loop, your brain sees "every other element" and agrees. Saying it out loud — "I'm adding index 0, then 2, then 4" — makes the mismatch audible.

Example 2: Async/await mistake

// Goal: fetch user, then fetch their orders, return combined object
async function getUserWithOrders(userId: string) {
  const user = await fetchUser(userId);
  const orders = fetchOrders(userId); // Duck: "I'm calling fetchOrders and storing the result in orders"
  // Duck question: "Wait, I said 'storing the result' — but fetchOrders is async.
  //                 Am I actually storing a Promise here, not the order data?"
  // Bug found: missing await before fetchOrders
  return { user, orders };
}

This bug is invisible to eyes that know fetchOrders is async. Narrating "storing the result" triggers the realization that the result isn't what you think it is.

Example 3: Wrong reference in a React state update

// Goal: toggle the completed state of a specific todo item
function toggleTodo(id: string) {
  setTodos(todos.map(todo => {
    if (todo.id === id) {
      return { ...todo, completed: !todo.completed };
    }
    return todo; // Duck: "For todos that don't match the id, I'm returning the original todo"
    // Duck question: "Am I returning 'todo' here, which is the current iteration variable?
    //                 Yes. That's correct — returning the unchanged object."
    // No bug here — but narrating confirmed the assumption rather than hoping
  }));
}

Sometimes the duck confirms your code is right. That's also valuable — it eliminates suspects quickly.

Example 4: Subtle scope issue with closures

// Goal: create an array of functions that each log their index when called
const funcs = [];
for (var i = 0; i < 3; i++) {
  funcs.push(function() {
    console.log(i); // Duck: "This function logs the value of i when called"
    // Duck question: "When is it called? After the loop. What is i after the loop? 3."
    // Duck question: "So all three functions will log 3, not 0, 1, 2?"
    // Bug found: var has function scope, not block scope. Use let instead.
  });
}

Classic JavaScript gotcha. Reading the loop, you see the intent. Narrating when i is evaluated makes the timing explicit.

When the Duck Isn't Enough

Rubber duck debugging is powerful, but it has real limits. Knowing when to stop talking to the duck is as important as using the technique in the first place.

The Limits of Solo Debugging

The rubber duck technique works best for logic errors you can trace — bugs that live within a single function or a small number of interacting components, where the full state is in your head. It works well for:

  • Off-by-one errors and loop boundary conditions
  • Async/await and Promise handling mistakes
  • Incorrect variable references and scope issues
  • Type coercion surprises ("5" + 1 vs. 5 + 1)
  • Simple state management bugs in React or similar

It works poorly — or not at all — for:

  • Build and deployment errors that involve environment-specific behavior (what you're running locally isn't what's failing in production). See how to debug Vercel build failures for a systematic approach to these.
  • Hydration errors that involve the server/client boundary. The bug exists in the interaction between two environments, not in any single code path you can narrate. The Next.js hydration error guide covers the specific patterns here.
  • Security policy errors like Row Level Security violations in Supabase, where the bug is in the policy, not the code, and the error surface is intentionally opaque. The Supabase RLS policy guide walks through how to actually diagnose these.
  • Flaky/intermittent failures — race conditions, network timing issues, concurrency bugs. These require observability, not verbal explanation.
  • AI-generated code with subtle semantic errors — the duck helps you check the logic, but if the AI produced code that looks right and is subtly wrong (wrong API arguments, deprecated function signatures, silently incorrect behavior), you may need someone who knows the library deeply. This is the same problem described in stopping Cursor AI lazy placeholders: the code looks complete but isn't.

Rubber Duck vs. Stack Overflow vs. AI Tools vs. FlowQL

| Method | Best for | Response time | Cost | Depth of understanding | |---|---|---|---|---| | Rubber duck | Logic errors you can narrate; first-pass debugging | Instant | Free | Depends entirely on you | | Stack Overflow | Known, documented errors with canonical fixes | Minutes to hours | Free | High for common errors; zero for novel ones | | AI tools (Cursor/Copilot/ChatGPT) | Boilerplate errors, syntax fixes, common patterns | Seconds | ~$20/month | Broad but shallow; misses context-specific bugs | | Pair programmer (colleague) | Most bugs; real-time back-and-forth | Depends on availability | Free if you have one | High, but interrupts their work | | FlowQL senior dev session | Hard bugs the duck + AI can't solve; production incidents; unfamiliar codebases | 30 min scheduled | Per session | Deep — a senior dev who has seen this exact class of bug before |

The pattern that works: duck first, AI second, human third. The duck is instant and free. AI tools catch the common cases the duck missed. When both fail, you need a human who can ask questions, look at your actual codebase, and apply pattern recognition from having debugged hundreds of similar problems.

Making Rubber Duck Debugging Part of Your Workflow

The technique is most effective when it's a habit, not a last resort. Here's how to integrate it systematically:

Before you write a bug report or ask for help: Make a rule — no posting to Slack, Stack Overflow, or an AI tool until you've explained the bug to the duck first. This forces you to articulate the problem precisely, which dramatically improves the quality of help you get when you do ask. It also filters out the bugs you could have found yourself in two minutes.

Before you submit a code review: Walk through your diff as if explaining it to a new hire. State what each changed line does and why. This catches the "wait, this doesn't do what I think it does" class of errors before a reviewer has to.

When you're stuck and frustrated: The duck is particularly useful when you're in an emotional spiral — you've been stuck for an hour, you feel stupid, you're about to randomly change things and see what happens. Starting from the top and narrating forces a reset. It's mechanical, which is exactly what you need when your judgment is compromised.

In combination with AI tools: The 80/20 reality of AI-assisted development is that AI handles the straightforward parts and you hit a wall on the hard 20%. Before prompting the AI with a bug, duck-debug first. Articulating the problem precisely makes your prompt dramatically better — you're describing what the code actually does rather than what you think it does. This is especially true for issues like Cursor getting stuck while thinking or context window limitations — in those cases, the AI itself is the bottleneck, and a clear problem statement is what you need for the next step.

Set a timer. If you're going to solo-debug, set a 20-minute limit. Explain to the duck for 20 minutes. If you haven't found it by then, the bug is in a category the duck can't solve — and you've already built a clear description of the problem ready for whoever you ask next.

Conclusion

Rubber duck debugging works. Not because of the duck — because of what explaining forces your brain to do. It slows down pattern-matching, surfaces hidden assumptions, and breaks the fixation that sets in after you've been staring at the same code for too long.

Use it before Googling. Use it before prompting your AI assistant. Use it before pasting code into a chat with a colleague. In most cases, you'll find the bug yourself. That's the point.

But there's a class of bugs the duck cannot solve: the ones that live in the interaction between systems rather than inside a single function. Production-only failures. Deep framework behavior. Policies and configs that error silently. Concurrency issues. The kind of bug where you've explained every line and it all makes sense on paper but something is still wrong.

For those bugs, you don't need a better duck. You need a senior engineer who has debugged this exact class of problem before, can ask you targeted questions about your specific stack, and can look at your codebase in real time — not just the snippet you pasted into a chat.

That's what a FlowQL session is: a 30-minute screen-share with a vetted senior developer. You explain the problem (just like you'd explain it to the duck), and instead of silence, you get back a diagnostic. When the rubber duck has heard everything and you're still stuck, that's when to book a session.

Still blocked?

This fix didn't work for your setup? Get a senior engineer on your screen in 30 minutes — fixed or refunded.

Reserve My Spot →
Still stuck?

Get a senior engineer on your screen.

30-minute live screen-share sessions with a vetted senior dev. Fixed or fully refunded — no questions asked.

No spam. Just a heads-up when sessions open.

Related Articles