skip to content

Breakpoints & Stepping

The debug gem, bundled since Ruby 3.1, stops at binding.break and steps through code under rdbg; Pry and byebug are the older choices. Interviewers ask how you would chase a live bug.

on this pageshow

explore

questions

5

In Ruby's debug gem, how do binding.break and the rdbg command start a debugging session, and what does require "debug" do?

level: middleimportance: must knowfreq 58%

answer

  1. bundled gem since Ruby 3.1
  2. binding.b and debugger are aliases
  3. require "debug" starts a nonstop session
  4. rdbg script.rb stops at line 1
  5. rdbg -c -- bundle exec rspec

basics

~20 s

The debug gem, bundled with Ruby since 3.1, defines binding.break (aliases binding.b and debugger) once require "debug" has started a session; execution stops there at an (rdbg) prompt. rdbg runs a script or, with -c, a command under the debugger without editing code.

solid answer

~40 s

`require "debug"` loads the debug gem and starts a session in **nonstop** mode (equivalent to `rdbg --nonstop --no-sigint-hook`), so the program runs normally until it reaches `binding.break`, `binding.b` or `debugger`, where it stops at an `(rdbg)` prompt showing the surrounding source and frames. Without the gem loaded, `binding.break` is undefined and raises `NoMethodError`. Alternatively, `rdbg tax_report.rb` runs a script under the debugger and **stops at its first line**, where you set breakpoints with `break`; `rdbg -c -- bundle exec rspec spec/tax_spec.rb` does the same for an executable on `PATH`, and the `--` keeps the command's own flags away from `rdbg`. Under `bundle exec`, the Gemfile must list `gem "debug"`. The gem replaced the old `lib/debug.rb` in Ruby 3.1.

code

ruby · 20 lines
ruby
require "debug"

class TaxCalculator
  RATES = {"DE" => 19, "FR" => 20}

  def total_for(order)
    net = order.sum { it[:price] * it[:qty] }
    binding.break if net > 1_000
    net + tax_on(net, order.first[:country])
  end

  private

  def tax_on(net, country)
    rate = RATES.fetch(country)
    (net * rate / 100.0).round(2)
  end
end

TaxCalculator.new.total_for([{price: 600, qty: 2, country: "UK"}])

go deeper

for a junior

Know that require "debug" plus binding.break stops the program at an (rdbg) prompt, and that binding.b and debugger mean the same.

for a middle

Explain nonstop mode versus rdbg's stop at start, rdbg -c with the -- separator, and why the gem must be in the Gemfile under Bundler.

for a senior

Choose between code breakpoints and rdbg -c for test suites and servers, and keep debugger gems and stray breakpoints out of production.

for a principal

Set a team default for debugging tooling so that local, CI and container workflows use one debugger consistently.

## The debug gem in one paragraph Ruby's current debugger is the **debug gem** (`debug`, version 1.11 with Ruby 4.0). It replaced the old, unmaintained `lib/debug.rb` in Ruby 3.1 and has been a **bundled gem** since then, so it is installed with Ruby but, like any gem, must be in the Gemfile when you run under `bundle exec`. It ships an executable, **`rdbg`**, and a library you can `require`. ## Starting from inside the code If you can edit the code, the usual flow is: 1. `require "debug"` near the start of the program (or in a test helper). 2. Put **`binding.break`** where you want to stop. **`binding.b`** and **`debugger`** are aliases of the same method. 3. Run the program as usual: `ruby tax_report.rb`. `require "debug"` does more than load code: it **starts a debug session** in nonstop mode, the same as `rdbg --nonstop --no-sigint-hook`. That means: - the program does not pause at its first line; - Ctrl-C is left to the program instead of dropping into the debugger; - each `binding.break` sets a one-shot breakpoint on the next line and stops there with an `(rdbg)` prompt, printing the source around the current line and the top frames. Two failure modes are worth recognising: | Situation | What happens | |---|---| | `binding.break` reached with no `require "debug"` and no `rdbg` | `NoMethodError`: `Binding#break` is defined only by the debug gem. | | Gem loaded (`require "debug/session"`) but no session started | `debugger` returns without stopping, because it checks for an active session first. | The first is the classic one: `binding.irb` works in any file because Ruby's prelude defines it lazily, but `binding.break` needs the gem loaded. ## Starting from the outside with rdbg If you would rather not edit code, run the program **under** the debugger: - `rdbg tax_report.rb` — like `ruby tax_report.rb`, but it **stops before the first line**. Set breakpoints (`break TaxCalculator#tax_on`), then `continue`. - `rdbg -n tax_report.rb` (`--nonstop`) — do not stop at the start; useful when breakpoints are already in the code. - `rdbg -c -- bundle exec rspec spec/tax_spec.rb` — `-c` (`--command`) means the first argument is an **executable on `PATH`** (`rake`, `rspec`, `bundle`, `rails`), not a Ruby file. The `--` separates `rdbg`'s options from the command's: `rdbg -c rake -T` would hand `-T` to `rdbg`. - `ruby -r debug/start tax_report.rb` — the library form of plain `rdbg`. While a program runs under `rdbg`, Ctrl-C suspends it into the console, which helps when it seems stuck. ## Library entry points at a glance | Require | Same as | |---|---| | `require "debug"` | `rdbg --nonstop --no-sigint-hook` | | `require "debug/start"` | `rdbg` (stop at start) | | `require "debug/open"` | `rdbg --open` (remote, stop at start) | | `require "debug/open_nonstop"` | `rdbg --open --nonstop` | ## Keeping it out of production - Put `gem "debug"` in the `:development, :test` groups only. - Remove `binding.break` lines before committing; a stop in a server or a CI job waits for console input that never comes. - Prefer `rdbg -c` for one-off sessions so nothing has to be edited and reverted.

  • Why does binding.break raise NoMethodError in a file where binding.irb works?
    Ruby's prelude defines `Binding#irb` and loads IRB on first use, so `binding.irb` works anywhere. `Binding#break` is defined only by the debug gem's session code, so it exists after `require "debug"`, `ruby -r debug`, or when the program runs under `rdbg`. Add the require (and `gem "debug"` under Bundler) or start the program with `rdbg`.
  • What is the difference between rdbg tax_calc.rb and rdbg -c -- rake tax:report?
    Without `-c`, the argument is a Ruby script run like `ruby tax_calc.rb`. With `-c`, the first argument is an executable found on `PATH`, such as `rake`, `rspec` or `bundle`, and it is launched with the debugger loaded. The `--` stops `rdbg` from parsing the command's own options.
  • Why might a program started with rdbg stop immediately even though you only wanted binding.break stops?
    Plain `rdbg` stops at the first line so you can set breakpoints before anything runs. Pass `-n` (`--nonstop`) to skip that initial stop, or type `continue` to run to the first breakpoint. `require "debug"` in the code uses nonstop mode by default.

saying these in an interview costs you the question

  • binding.break works in any Ruby 4.0 file without requiring debug.
  • require "debug" pauses the program at its first line.
  • rdbg -c expects a Ruby file path rather than an executable name.
  • The debug gem is Ruby's old lib/debug.rb under a new name.
  • Under bundle exec, require "debug" works even when the Gemfile omits the gem.
open as a page

At a Ruby debug gem (rdbg) prompt, what do step, next, finish and continue do, and how do info and bt help?

level: juniorimportance: should knowfreq 48%

basics

~20 s

In the debug gem, step enters the next call, next runs to the next line of the same method, finish runs until the current method returns, and continue runs to the next breakpoint. info lists locals; bt prints the call stack.

open as a page

In Ruby's debug gem, how do the break and catch commands set breakpoints without editing code, including conditional ones?

level: middleimportance: should knowfreq 36%

basics

~20 s

In the debug gem, break sets a breakpoint on a line or a method (break tax_calc.rb:15, break TaxCalculator#tax_on) and accepts if: for a condition; catch KeyError stops when that exception is raised, even if it is rescued later.

open as a page

In Ruby, how do binding.pry, pry-byebug and byebug relate to the debug gem, and which would you pick for a new Ruby 4.0 project?

level: middleimportance: should knowfreq 42%

basics

~20 s

binding.pry opens Pry, a richer console with no stepping; pry-byebug adds byebug's stepping to it; byebug is a standalone C-extension debugger. For a new Ruby 4.0 project the default is the debug gem, bundled with Ruby, with stepping, remote attach and editor integration.

open as a page

A Ruby service runs in a container without a TTY; how do you debug it with the debug gem's rdbg --open and rdbg -A, and what are the risks?

level: seniorimportance: should knowfreq 24%

basics

~20 s

Start the program as a debuggee with rdbg --open (or require "debug/open"), then connect a console with rdbg -A. It listens on a UNIX socket by default or TCP with --port; the channel is unencrypted and grants code execution, so keep it local.

open as a page