As we continue exploring IEx capabilities, let's focus on its powerful debugging features. This article will show you practical techniques for debugging Elixir applications using IEx.
Table of Contents
- Interactive Debugging with IEx.pry
 - Debugging Real Applications
 - Advanced Debugging Techniques
 - Best Practices
 - Conclusion
 - Next Steps
 
Interactive Debugging with IEx.pry
The IEx.pry/0 function is one of the most powerful debugging tools in Elixir. Let's see it in action with a practical example:
# user_points.ex
defmodule UserPoints do
  require IEx  # Required to use IEx.pry
  def calculate_bonus(points) do
    bonus =
      points
      |> multiply_points()
      |> apply_threshold()
      |> (fn points ->
        IEx.pry()  # Add breakpoint here
        round_points(points)
      end).()
    {:ok, bonus}
  end
  defp multiply_points(points) do
    points * 1.5
  end
  defp apply_threshold(points) when points > 1000 do
    1000
  end
  defp apply_threshold(points), do: points
  defp round_points(points) do
    round(points)
  end
end
To use this in practice:
- Save the code in 
user_points.ex - Start IEx with the code:
 
iex user_points.ex
- Try the function:
 
iex(1)> UserPoints.calculate_bonus(800)
Break reached: UserPoints.calculate_bonus/1 (user_points.ex:10)
    7:       |> multiply_points()
    8:       |> apply_threshold()
    9:       |> (fn points ->
   10:         IEx.pry() # Add breakpoint here
   11:         round_points(points)
   12:       end).()
   13:
# Let's analyze the flow to this point:
iex(2)> points  # Current value after multiply_points and apply_threshold
1000
# We can also check intermediate calculations:
iex(3)> 800 * 1.5  # What multiply_points did
1200.0
# Since 1200.0 > 1000, apply_threshold returned 1000
iex(4)> continue  # Let's continue execution
{:ok, 1000}  # Final result
At the breakpoint, we can:
- Inspect variables (like 
points) - Execute code in the current context
 - Use 
continueto resume execution 
Debugging Real Applications
Let's look at a more complex example - a shopping cart system with multiple discount rules:
# shopping_cart.ex
defmodule ShoppingCart do
  defstruct items: [], discounts: [], total: 0
  def new, do: %ShoppingCart{}
  def add_item(cart, item) do
    require IEx; IEx.pry()  # Debug point 1
    %{cart | items: [item | cart.items]}
    |> calculate_total()
    |> apply_discounts()
  end
  def calculate_total(%{items: items} = cart) do
    total = Enum.reduce(items, 0, fn
      %{price: price, quantity: quantity}, acc ->
        acc + (price * quantity)
    end)
    %{cart | total: total}
  end
  def apply_discounts(%{total: total, items: items} = cart) do
    require IEx; IEx.pry()  # Debug point 2
    discounts = [
      quantity_discount(items),
      total_discount(total)
    ]
    %{cart | 
      discounts: discounts,
      total: apply_discount_values(total, discounts)
    }
  end
  defp quantity_discount(items) do
    total_quantity = Enum.reduce(items, 0, & &1.quantity + &2)
    case total_quantity do
      q when q >= 5 -> {:quantity, 0.1}
      _ -> {:quantity, 0}
    end
  end
  defp total_discount(total) do
    case total do
      t when t >= 100 -> {:total, 0.15}
      _ -> {:total, 0}
    end
  end
  defp apply_discount_values(total, discounts) do
    Enum.reduce(discounts, total, fn
      {_type, value}, acc -> acc * (1 - value)
    end)
  end
end
Let's debug this cart system:
# Start IEx with the module
iex(1)> c("shopping_cart.ex")
    warning: redefining module ShoppingCart (current version defined in memory)
[ShoppingCart]
iex(2)> cart = ShoppingCart.new()
%ShoppingCart{items: [], discounts: [], total: 0}
iex(3)> item = %{name: "Phone", price: 500, quantity: 2}
%{name: "Phone", price: 500, quantity: 2}
iex(4)> ShoppingCart.add_item(cart, item)
Break reached: ShoppingCart.add_item/2 (shopping_cart.ex:7)
    4:   def new, do: %ShoppingCart{}
    5:
    6:   def add_item(cart, item) do
    7:     require IEx; IEx.pry()  # Debug point 1
    8:     %{cart | items: [item | cart.items]}
    9:     |> calculate_total()
   10:     |> apply_discounts()
# At Debug point 1:
iex(5)> cart
%ShoppingCart{items: [], discounts: [], total: 0}
iex(6)> item
%{name: "Phone", price: 500, quantity: 2}
iex(7)> continue
Break reached: ShoppingCart.apply_discounts/1 (shopping_cart.ex:22)
   19:   end
   20:
   21:   def apply_discounts(%{total: total, items: items} = cart) do
   22:     require IEx; IEx.pry()  # Debug point 2
   23:     discounts = [
   24:       quantity_discount(items),
   25:       total_discount(total)
# At Debug point 2:
iex(8)> cart.total
1000
iex(9)> continue
%ShoppingCart{
  items: [%{name: "Phone", price: 500, quantity: 2}],
  discounts: [quantity: 0, total: 0.15],
  total: 850.0
}
Advanced Debugging Techniques
1. Using IO.inspect/2
IO.inspect/2 is a non-blocking way to debug your code by inspecting values as they flow through your functions:
def process_order(items) do
  items
  |> IO.inspect(label: "Input items")
  |> calculate_total()
  |> IO.inspect(label: "After total calculation")
  |> apply_discounts()
  |> IO.inspect(label: "After discounts")
end
# Example output:
# Input items: [%{name: "Phone", price: 500, quantity: 2}]
# After total calculation: 1000
# After discounts: 850.0
2. Conditional Debugging
You can use environment variables to control when debugging code runs:
defmodule UserPoints do
  require IEx
  def calculate_bonus(points) do
    bonus =
      points
      |> multiply_points()
      |> debug_threshold()
      |> round_points()
    {:ok, bonus}
  end
  defp debug_threshold(points) do
    if System.get_env("DEBUG") do
      IEx.pry()
    end
    apply_threshold(points)
  end
  # ... rest of the module
end
To use conditional debugging:
# Normal execution
$ iex user_points.ex
iex(1)> UserPoints.calculate_bonus(800)
{:ok, 1000}
# With debugging enabled
$ DEBUG=true iex user_points.ex
iex(1)> UserPoints.calculate_bonus(800)
# Will break at the debug point
Best Practices
- 
Strategic Breakpoints
- Place 
IEx.pry()at critical decision points - Remove debugging code before committing
 - Use meaningful variable names at debug points
 
 - Place 
 - 
Effective Debugging Session
- Start with high-level inspection
 - Check intermediate values
 - Use IO.inspect for passive debugging
 - Keep track of the execution flow
 
 - 
Code Organization
- Keep debugging code clearly marked
 - Use version control branches for debugging sessions
 - Document discovered issues
 - Clean up debugging code after use
 
 - 
Development Workflow
- Use 
IEx.pry()for interactive debugging - Use 
IO.inspect/2for passive debugging in pipelines - Add temporary debugging code in development
 - Use comprehensive tests to prevent bugs
 
 - Use 
 
Conclusion
IEx.pry() is a powerful tool for interactive debugging in Elixir. Combined with other techniques like IO.inspect/2 and strategic breakpoints, it provides a robust debugging experience.
Remember that these are development tools - they should be used carefully in production environments.
Next Steps
- Practice debugging with your own projects
 - Learn about debugging strategies for different types of problems
 
              
    
Top comments (0)