skip to content

Interactive Console

IRB is Ruby's console: binding.irb drops into a live scope, and commands like show_source, ls and edit inspect code. Interviewers ask how you explore an object or reproduce a bug quickly.

on this pageshow

explore

questions

6

In Ruby, what happens when execution reaches binding.irb, and how do you resume or stop the program afterwards?

level: juniorimportance: must knowfreq 68%

answer

  1. a prompt opened inside a live scope
  2. locals, self and ivars are real
  3. whereami (alias @) reprints the code
  4. exit resumes, exit! ends the process
  5. disable_irb for a breakpoint in a loop

basics

~20 s

binding.irb pauses the program at that line and opens an IRB prompt inside that scope, with its locals, self and instance variables live. exit (or Ctrl-D) resumes the program from that line; exit! ends the process.

solid answer

~40 s

`binding` returns the current execution context and `Binding#irb` starts an IRB session on it, so the program stops at that line and prints the surrounding code. At the prompt you can read and even reassign locals, call private methods of `self`, and inspect instance variables, because you are evaluating inside that scope, not in a copy. `whereami` (alias `@`) shows the code again. `exit` (or Ctrl-D) leaves the session and the program **continues** from the line after `binding.irb`; `exit!` ends the whole process through `Kernel.exit`. If the breakpoint sits in a loop, `disable_irb` turns every later `binding.irb` into a no-op for the rest of the run. Ctrl-C does not leave the session by default.

code

ruby · 21 lines
ruby
require "csv"

class CsvImporter
  def initialize
    @errors = []
  end

  def import(text)
    CSV.parse(text, headers: true).each_with_index do |row, i|
      binding.irb if row["email"].to_s.empty?
      @errors << i unless valid?(row)
    end
    @errors
  end

  private

  def valid?(row) = row["email"].to_s.include?("@")
end

CsvImporter.new.import("name,email\nAda,[email protected]\nBob,\n")

go deeper

for a junior

Recall that binding.irb opens a prompt in the live scope, that exit resumes the program, and that exit! ends it. Show you can read locals and instance variables there.

for a middle

Explain that the prompt evaluates inside the captured Binding, so assignments and side effects are real, and use conditional breakpoints or disable_irb for loops.

for a senior

Talk about keeping breakpoints out of committed code, how a stray prompt hangs a server or CI job, and when to escalate from binding.irb to IRB's debug command.

for a principal

Weigh ad hoc breakpoints against logging and reproducible tests as a team habit: which debugging workflows you standardise, and how you keep console access out of production paths.

## What binding.irb actually is Ruby's `Kernel#binding` returns a **`Binding`**: an object that captures an execution context at one point in a program — its local variables, the value of `self`, and the method and block it belongs to. IRB, Ruby's interactive console, adds a method to that object: **`Binding#irb`**. Calling `binding.irb` in the middle of your code therefore means "open an IRB prompt that evaluates everything I type *inside this exact context*". In Ruby 4.0 IRB is a **bundled gem** rather than part of the standard library, but you still do not need a `require` line: Ruby's prelude defines a small `Binding#irb` that requires `irb` on first use, and if a plain `require` fails under Bundler it force-activates the installed bundled copy. Listing `gem "irb"` in the Gemfile's development group is still the tidy choice for a project. ## What you see when it fires When execution reaches the line, IRB: 1. prints `From: file.rb @ line N` and a few lines of code around the call, with an arrow on the current line (pass `binding.irb(show_code: false)` to skip this); 2. opens a prompt whose label shows the current `self`, for example `irb(#<CsvImporter:0x...>):001>`; 3. waits: the rest of the method has **not** run yet. At the prompt you are not looking at a snapshot. You can: - read locals such as `row` or `line_no` by name; - **assign** to them, and the program sees the new value when it resumes; - read `@instance_variables` of `self` and call its **private** methods without a receiver; - call any code the program could call at that point, including methods with side effects (a database write is a real write). `whereami` (alias `@`) reprints the surrounding code after your own output has scrolled it away. ## Leaving the session | You type | What happens | |---|---| | `exit` or Ctrl-D | The IRB session ends and the program **continues** from the line after `binding.irb`. | | `exit!` | IRB calls `Kernel.exit`: a `SystemExit` ends the process, so `ensure` blocks and `at_exit` hooks still run. | | `disable_irb` | Redefines `Binding#irb` as an empty method for the rest of the process, then leaves the session and continues. | | Ctrl-C | Interrupts the current input only; `IRB.conf[:IGNORE_SIGINT]` is `true` by default, so the session stays open. | The most common surprise is `exit`: newcomers expect it to stop the program, as it would in a plain script, and are surprised when the program carries on and perhaps hits the breakpoint again. ## Breakpoints in loops and hot paths A `binding.irb` inside `each` stops on **every** iteration. Three ways out: - make it conditional: `binding.irb if row["email"].nil?` stops only on the row you care about; - type `disable_irb` once you have seen enough, so later hits are skipped; - type `exit!` to end the run altogether. A conditional breakpoint is usually the right first move: when a CSV import fails on row 4,812, you want the prompt on that row, not on the first one. ## A worked example Suppose `CsvImporter#import_row` is rejecting a row and you do not know why. Put `binding.irb if row["email"].to_s.empty?` inside the method and run the import. When the prompt opens you can type `row` to see the parsed fields, `row.to_h` to see headers and values together, `@errors` to see what the object has collected so far, and call a private helper like `normalize(row)` to watch it return. Fix a local (`row["email"] = "[email protected]"`), type `exit`, and the method carries on with the corrected value — a quick way to confirm a hypothesis before editing the file. ## Good habits - Treat `binding.irb` as temporary: remove it before committing, because a forgotten one can leave a server or a CI job waiting for console input. - Prefer a conditional breakpoint to a bare one inside loops. - Remember that anything you call runs for real; inspect before you mutate. - When you need to step line by line from the prompt, IRB's `debug` command hands the same session to the debug gem.

  • If you assign to a local variable at the binding.irb prompt, does the running method see the change after exit?
    Yes. The prompt evaluates inside the captured binding, not a copy, so assigning `row = other_row` or mutating `row["email"]` changes what the method uses when it resumes. That is what makes `binding.irb` useful for testing a fix in place — and why side-effecting calls at the prompt are real.
  • Your binding.irb sits inside a loop over 10,000 rows. How do you avoid typing exit ten thousand times?
    Make the breakpoint conditional (`binding.irb if row["email"].nil?`) so it fires only for the row you care about, or type `disable_irb` at the prompt, which redefines `Binding#irb` as a no-op for the rest of the process and lets the run finish. `exit!` ends the process instead.
  • Does exit! at an IRB prompt skip ensure blocks and at_exit hooks like Kernel#exit! does?
    No. IRB's `exit!` command leaves the session and then calls `Kernel.exit`, which raises `SystemExit`, so `ensure` clauses and `at_exit` hooks still run. It is not the same as calling `Kernel#exit!` in your own code, which terminates immediately without them.

binding.irb is like pausing a film and stepping into the scene: you can pick up the props and move them, and when you press play the story continues with whatever you changed.

saying these in an interview costs you the question

  • Typing exit at the binding.irb prompt stops the whole program.
  • The prompt works on a copy, so assigning a local changes nothing.
  • Private methods of self cannot be called from the binding.irb prompt.
  • Ctrl-C is the normal way to leave a binding.irb session.
  • binding.irb needs require "irb" at the top of every file.
open as a page

In an IRB session, which built-in commands let you explore an unfamiliar object's methods, source code and documentation without leaving the console?

level: middleimportance: must knowfreq 52%

basics

~20 s

IRB's ls lists an object's methods, constants and variables (-g filters them); show_source prints a Ruby method's definition; show_doc looks up its RDoc; cd moves self into an object; edit opens the file in your editor; help lists them all.

open as a page

In IRB, what does the underscore variable _ hold, and what do __ and IRB.conf[:EVAL_HISTORY] add?

level: juniorimportance: should knowfreq 38%

basics

~10 s

In IRB, _ holds the value of the last evaluated expression, or the exception object if that input raised. __ is the numbered history of results, and exists only after setting IRB.conf[:EVAL_HISTORY] or conf.eval_history.

open as a page

Inside an IRB session opened by binding.irb, what does IRB's debug command do, and when does it refuse to start?

level: middleimportance: should knowfreq 30%

basics

~20 s

IRB's debug command hands the paused binding.irb session to the debug gem: the prompt becomes irb:rdbg and commands like next and step work beside IRB's own. It refuses outside binding.irb, in multi-irb mode, or without a loadable debug gem.

open as a page

In Ruby 4.0's IRB, which rc files run at startup, in what order, and why is an .irbrc inside a cloned repository a risk?

level: seniorimportance: should knowfreq 22%

basics

~20 s

IRB loads every rc file it finds: the IRBRC path, the XDG config file, ~/.irbrc or ~/.config/irb/irbrc, then .irbrc in the current directory. Each is plain Ruby, so a repository's .irbrc runs arbitrary code; irb -f skips them.

open as a page

In Ruby 3.4 and later, how does IRB's type-based completion differ from its regexp completor, and why can it fall back under Bundler?

level: middleimportance: nice to knowfreq 16%

basics

~20 s

Since Ruby 3.4 IRB defaults to IRB::TypeCompletor, which uses the repl_type_completor gem and RBS to infer receiver types, so chained calls and block parameters complete. If that gem cannot load, as under a Gemfile that omits it, IRB quietly uses IRB::RegexpCompletor.

open as a page