When building Ruby applications, you'll inevitably encounter bugs that require more than print statements to diagnose. Effective debugging means understanding exactly what your code does at each step, examining variable states, and tracing execution flow through complex logic.
Byebug is a debugger for Ruby 2.x and 3.x that provides essential debugging capabilities including breakpoints, step-by-step execution, and stack navigation. It's built as a standalone gem that works independently of any REPL or interactive shell.
With Byebug, you can halt program execution at specific points, inspect the current state of variables, navigate through the call stack, and control how your code executes line by line.
This article will show you how to use Byebug to debug Ruby applications effectively.
Prerequisites
Before you start, ensure you have:
- Ruby 2.0+ installed on your machine
- Basic familiarity with Ruby syntax and application structure
Setting up the project directory
You'll create a workspace for practicing debugging techniques with Byebug.
Create a directory for the debugging examples and navigate into it:
Initialize a new bundle to manage dependencies:
Open the generated Gemfile and add Byebug:
Install the gem:
Byebug is now available for your debugging sessions, isolated from other Ruby projects on your system.
Understanding Byebug basics
Byebug operates differently from interactive shells like pry. While pry provides a full REPL environment, Byebug focuses specifically on debugging capabilities. This makes it lightweight and straightforward for traditional debugging workflows.
Create a simple Ruby script to explore Byebug's features. Save this as temperature_converter.rb:
Running this script normally produces temperature conversions:
To debug with Byebug, you need to insert breakpoints directly in your code. There's no command-line option to launch Byebug like some other debuggers.
Adding breakpoints to your code
Byebug requires you to explicitly place breakpoints in your source code where you want execution to pause. This approach gives you precise control over which parts of your application you want to inspect.
Modify your temperature converter to include a breakpoint:
Run the modified script:
Execution stops at the byebug statement:
When Byebug activates:
- It displays the code around your current location with line numbers
- An arrow
=>marks the next line to execute - The
(byebug)prompt appears, waiting for your commands - You have access to all variables in the current scope
Check the value of the celsius parameter:
Byebug provides several essential commands for controlling execution:
continueorc: Resume execution until the next breakpoint or program endnextorn: Execute the current line and move to the next linestepors: Step into method callsfinishorfin: Execute until the current method returnsquitorq: Exit the debugger and terminate the programhelp: Display available commands
Execute the current line and move forward:
Check the calculated result:
Continue execution to hit the breakpoint again for the next temperature:
The breakpoint triggers again. Check the new value:
Type quit to exit the debugger and stop the program entirely.
Inspecting variables and expressions
Byebug lets you examine not just simple variables but also complex expressions and object properties. This capability helps you understand the exact state of your application at any point during execution.
Create a new script that works with more complex data:
Run the script:
When execution pauses, you can inspect the grades array:
You can evaluate Ruby expressions directly:
The display command automatically shows an expression's value after each step:
Now execute the next line:
Check the total variable:
You can also inspect method calls before executing them:
To remove the display expression:
The info command shows useful debugging information. View all local variables:
These inspection capabilities help you understand exactly what's happening in your code without making assumptions.
Navigating the call stack
When debugging applications with nested method calls, you need to understand not just the current method but also how execution arrived there. Byebug's stack navigation commands let you examine different levels of the call stack.
Create a script with a deeper call hierarchy:
Run the script:
Execution pauses in the calculate_total method:
Check the calculated values:
The backtrace command shows the complete call stack:
The up command moves up one level in the call stack:
You're now in the process_order method's frame. Access its variables:
Move up another level:
The down command moves back down the stack:
To see which frame you're currently in:
This navigation capability helps you understand the execution context and trace how data flows through your application.
Controlling execution with step commands
Byebug provides fine-grained control over how your code executes. You can step into methods to see their internal workings or skip over them when you're confident they work correctly.
Add a breakpoint to your order processor to demonstrate stepping:
Run the script:
At the breakpoint, use next to move to the next line:
The step command enters the method call:
You're now inside calculate_tax. Check the parameters:
Execute the calculation:
The finish command completes the current method and returns to the caller:
You're back in calculate_total. Check the tax value:
These stepping commands give you complete control over execution flow, allowing you to dive deep into problematic code or skip past sections you trust.
Final thoughts
This article has shown how Byebug simplifies the process of debugging Ruby applications by giving you complete control over code execution. With its ability to pause programs at specific points, inspect variable states, and step through logic line by line, Byebug turns debugging into a structured, efficient workflow.
By learning to navigate the call stack, evaluate expressions, and use commands like next, step, and finish, you can pinpoint issues quickly and understand how your code behaves in detail.
Ultimately, Byebug equips you with the tools to move beyond guesswork, helping you trace, analyze, and fix bugs more effectively while improving your overall Ruby development workflow.