The letter A styled as Alchemists logo. lchemists
Published December 1, 2023 Updated October 13, 2025
Cover
Interactive Ruby (IRB)

Interactive Ruby (IRB) is a default gem that comes bundled with Ruby and provides a quick way to experiment with Ruby code within within your console. You’re not limited to tinkering with Ruby within your console, powerful as that is, you can also customize IRB using your own configuration in terms of prompt, colors, and even build your own extensions.

Let’s explore IRB’s features so you can maximize all of what IRB provides to enhance your workflow further.

Setup

To get started, ensure you are using IRB 1.15.0 or higher. Example:

gem list irb
# irb (default: 1.15.2)

💡 Since IRB is a default gem, you can enforce the latest version by updating your Default Gems (as shown in the output above).

Next, you’ll want to create an .irbrc file which, for the uninitiated, ends up in the root of your $HOME directory. Unfortunately, placing all of your Dotfiles in your home directory is messy and difficult to maintain. A saner and more manageable solution is to use the XDG specification and create your configuration here: $HOME/.config/irb/irbrc. To use a XDG configuration, add the following to your Bash profile (or whatever shell you are using) as follows:

export XDG_CONFIG_HOME="$HOME/.config"

Ensure you run exec $SHELL after the above has been applied so your shell picks up the changes and then create a $HOME/.config/irb/irbrc via your favorite editor.

Configuration

With the latest IRB version installed, we can start configuring IRB. The following sections will use my irbrc configuration as a blueprint so you can customize further if desired.

Prompt

The default IRB prompt is rather boring:

irb
# irb(main):001:0>

We can do better! In my irbrc, you’ll notice I use the IRB Kit gem which provides a custom prompt via the following code:

IRB.conf[:PROMPT][:ALCHEMISTS] = {
  PROMPT_I: "[#{IRB::Kit.prompt}]> ",
  PROMPT_N: "[#{IRB::Kit.prompt}]| ",
  PROMPT_C: "[#{IRB::Kit.prompt}]| ",
  PROMPT_S: "[#{IRB::Kit.prompt}]%l ",
  RETURN: "=> %s\n"
}

IRB.conf[:PROMPT_MODE] = :ALCHEMISTS

This custom prompt checks for Hanami, Rails, and then falls back to Ruby if no framework is found. For complete details, check out the IRB Kit documentation.

Auto Completion

You can use Reline::Face.config via the Reline gem to configure IRB with a color scheme you enjoy instead of using the default. I’m currently using green highlights against a black background with a white scrollbar:

require "irb/completion"

Reline::Face.config :completion_dialog do |config|
  config.define :default, foreground: :white, background: :black
  config.define :enhanced, foreground: :black, background: :green
  config.define :scrollbar, foreground: :bright_white, background: :black
end

Example:

Cover

For additional details, see the Reline Face documentation to customize further.

In addition to configuring your IRB auto completion look and feel, you can add type completion by adding the following line to your configuration:

IRB.conf[:COMPLETOR] = :type

Example:

Cover

💡 Ensure you install the ReplTypeCompletor gem in order to leverage RBS type signatures. This’ll prevent warnings showing in up in your console stating that the gem isn’t present.

Return Value Omission

In some situations, it can be nice to ignore the return value from an expression. This can be done by ending the expression with a semicolon. Example:

# Will fill your screen with the result.
result = "demo " * 100_000

# Will ignore the return value.
result = "demo " * 100_000;

Aliases

IRB supports aliases for commands you use the most. As per my irbrc configuration, I use the following:

IRB.conf[:COMMAND_ALIASES]
   .merge! b: :backtrace,
           c: :continue,
           e: :edit,
           h: :show_cmds,
           i: :info,
           l: :ls,
           n: :next,
           m: :measure,
           s: :step,
           w: :whereami

This allows me to use single letters for reduced typing. These aliases use a similar mapping to the aliases provided by the Debug gem so there is a natural flow when toggling between IRB and Debug when inspecting/debugging Ruby code.

💡 Your custom aliases will show up when showing help documentation (i.e. show_cmds).

Pager

Pagination is enabled, by default, but can be disabled by using irb --no-pager or adding the following to your irbrc configuration:

IRB.conf[:USE_PAGER] = false

History

Enabling a long history allows you to have more information you can reuse or capture for documentation purposes. Here’s what I have in my irbrc file:

IRB.conf[:EVAL_HISTORY] = 1_000
IRB.conf[:HISTORY_FILE] = "#{Dir.home}/.cache/irb/history.log"

Setting EVAL_HISTORY allows for 1,000 entries in your history file before being truncated. Setting HISTORY_FILE to a path of your choice allows you to not clutter your $HOME directory. In my case, an XDG cache path is used to store all of this information. Highly recommend since the XDG specification provides a sane — and clean — way to organize your Dotfiles.

Once you’ve applied your history configuration, you can use the up arrow to cycle through your history or type the history command to page through your history.

Amazing Print

To view your current IRB configuration with or without the Amazing Print gem, run the following within your IRB console:

# Without Amazing Print
IRB.conf.each { |key, value| puts "#{key}=#{value}" }

# With Amazing Print
require "amazing_print"
ap IRB.conf

Inspection

Should you ever need to know the version and configuration you are using, you can jump into an IRB console and use irb_info to print details. For example, at the time of this writing, here’s what I’m using:

Cover

Debugging

There are two ways you can debug by using an IRB or Debug break point:

# IRB
binding.irb

# Debug
binding.break

The advantage of using IRB is there is nothing to require. You can throw a break point in any file, run the code, and immediately start inspecting at the break point. With the Debug gem, you’ll need to require the gem first. All of my projects use the Debug gem so this isn’t a problem but can get cumbersome when working on a project that doesn’t require the Debug gem so you can use IRB’s binding.irb break point as a fallback in those situations. IRB’s debugger isn’t as feature rich as the Debug gem so definitely recommend requiring the Debug gem for a better experience.

IRB has a close integration with the Debug gem which means that if you start with a binding.irb breakpoint — and the Debug gem is required — you can switch to using Debug by typing debug in your console. Otherwise, you’ll end up with the session being abruptly ended.

Definitely check out IRB's documentation for more information.

Source Inspection

IRB provides several commands for importing code into your IRB session. Here’s the breakdown:

  • source: Loads given file into current session and displays each line.

  • irb_load: Same as the above but works like Kernel#load.

  • irb_require: Same as the above but works like Kernel#require.

Given the above, let’s say we have the following implementation (i.e. demo.rb) relative to the current directory in which you are using IRB:

# frozen_string_literal: true

module Demo
  def self.say = puts "HI"
end

We can launch an IRB console and see slightly different behavior:

Source

[3.2.2]> source "demo.rb"
[3.2.2]> # frozen_string_literal: true
=> nil
[3.2.2]>
[3.2.2]> module Demo
[3.2.2]|     def self.say = puts "HI"
[3.2.2]|   end
=> :say
[3.2.2]>
=> nil

irb_load

[3.2.2]> irb_load "#{Dir.pwd}/demo.rb"
[3.2.2]> # frozen_string_literal: true
=> nil
[3.2.2]>
[3.2.2]> module Demo
[3.2.2]|     def self.say = puts "HI"
[3.2.2]|   end
=> :say
[3.2.2]>
=> nil

irb_require

[3.2.2]> irb_require "./demo.rb"
=> true

You’ll notice that each command requires a different path syntax for each file being loaded and only source and irb_load print out each line of the file as the line is being evaluated. In all cases, once the file is parsed, you can immediately make use of the implementation by messaging Demo.say in your console to get expected output.

Show Source

The show_source command helps you view the source code of a method you are debugging. Additionally, you can use the -s option to move up a level and view the superclass source code. Using -ss allows you move up to the grandparent and so forth.

Measurements

IRB makes measuring code convenient via the measure command. Here’s an example of performing a time measurement (default) within an IRB console:

measure
# TIME is added.

(1..1_000_000).sum
# processing time: 0.000102s
# => 500000500000

measure :off
# => nil

With the above, only a single operation is measured but you could choose to leave measure on until you are done performing multiple measurements.

You can also use Stackprof by passing :stackprof as an argument to measure but I’ll leave that up to you to experiment with further.

By default, IRB uses the MEASURE_PROC key to store measurement operations. You can inspect the configuration by running the following in your IRB console (truncated for brevity):

IRB.conf[:MEASURE_PROC]

# {
#   TIME: #<Proc:0x000000010b5c5ba0 irb/init.rb:118>,
#   STACKPROF: #<Proc:0x000000010b5c5b78 irb/init.rb:128>
# }

Each measurement operation takes five positional parameters:

  1. context (IRB::Context): Captures the current IRB session.

  2. code (String): The code snippet to measure.

  3. line_number (Integer): The IRB console line number which increments with each new IRB entry.

  4. *arguments: Additional arguments which are passed on to your measure operation.

  5. block (Proc): The block of code you want to measure in case typing each line in IRB is too cumbersome.

Given the above, here’s a simple example of measuring Garbage Collection stats using the Refinements gem to calculate the diff between each measurement. You can copy and paste the following in your IRB console:

require "refinements"

using Refinements::Hash

IRB.conf[:MEASURE_PROC][:GC] = lambda do |_context, _code, _line_number, *arguments, &block|
  before = GC.stat
  block.call
  after = GC.stat

  puts "Garbage Collection Diff:"
  puts before.diff(after)
end

Then, to use the above while still in your IRB console, you can run as follows:

measure :gc
# GC is added.

(1..1_000_000).sum
# Garbage Collection Diff:
# {
#   heap_live_slots: [153300, 153370],
#   heap_free_slots: [736819, 736749],
#   total_allocated_objects: [986171, 986241],
#   malloc_increase_bytes: [528848, 532832],
#   oldmalloc_increase_bytes: [10737264, 10741248]
# }
# => 500000500000

Again, the above is a simple example but there is plenty of potential for adding multiple custom measurements that meet your needs.

Extensions

IRB Extensions were added in 1.13.0 which allows you to build custom commands and helpers without having to shove everything into your irbrc file. The design of these extensions definitely has flaws:

  • Commands: These don’t adhere to the Command Pattern so you have to use #execute which makes it hard to use closures (i.e. procs, lambdas, and/or methods).

  • Helpers: These also don’t adhere to the Command Pattern and, worse, use the Singleton pattern which means you can’t inject dependencies for improved testing.

Despite these rough edges, being able to implement extensions allows for sharing and reducing the lines of code in your IRB configuration. If you’d like functionality that leverages the new IRB Extensions, then check out my IRB Kit gem which can enhance your workflow further.

Easter Egg

This is silly — and you have to violate object encapsulation — but you can view the hidden easter egg by running the following:

IRB.__send__ :easter_egg

…​which will then produce the following animation:

Easter Egg

Conclusion

You’ve learned the core functioinality of IRB as well as how to fold IRB into your personal workflow. Keep an eye on this space because, with so much active development going on, there’s bound to be new features and/or improvements beyond what we’ve learned here.