Reason #34 • February 3rd, 2026

Exception handling: retry

An interesting feature of Ruby's exception handling is the retry keyword, which can be used inside a rescue block to restart the associated begin block or method from the beginning. As the name suggests, it is typically used to retry an operation that failed due to a transient error:

Ruby
NetworkError = Class.new(StandardError)

def flaky_network_request
  raise NetworkError if rand < 0.6

  "Success ✅"
end

def perform_request_with_retries(max_retries = 3)
  retries ||= 0
  response = flaky_network_request
  puts response
rescue NetworkError
  retries += 1
  if retries <= max_retries
    puts "  Retrying: attempt #{retries} out of #{max_retries}"
    retry
  end

  puts "Failure ⛔️"
end

10.times { perform_request_with_retries }

# Example output:
# Success ✅
# Success ✅
# Success ✅
# Success ✅
#   Retrying: attempt 1 out of 3
# Success ✅
#   Retrying: attempt 1 out of 3
#   Retrying: attempt 2 out of 3
#   Retrying: attempt 3 out of 3
# Failure ⛔️
#   Retrying: attempt 1 out of 3
#   Retrying: attempt 2 out of 3
# Success ✅
# Success ✅
#   Retrying: attempt 1 out of 3
# Success ✅
# Success ✅
      
JavaScript
class NetworkError extends Error {}

function flakyNetworkRequest() {
  if (Math.random() < 0.6) {
    throw new NetworkError();
  } else {
    return "Success ✅";
  }
}

function performRequestWithRetries(maxRetries = 3) {
  let retries = 0;
  while (true) {
    try {
      const response = flakyNetworkRequest();
      console.log(response);
      break;
    } catch (e) {
      if (e instanceof NetworkError) {
        retries += 1;
        if (retries <= maxRetries) {
          console.log(`  Retrying: attempt ${retries} out of ${maxRetries}`);
        } else {
          console.log("Failure ⛔️");
          break;
        }
      } else {
        throw e;
      }
    }
  }
}

for (let i = 0; i < 10; i++) {
  performRequestWithRetries();
}

// Example output:
// Success ✅
// Success ✅
// Success ✅
// Success ✅
//   Retrying: attempt 1 out of 3
// Success ✅
//   Retrying: attempt 1 out of 3
//   Retrying: attempt 2 out of 3
//   Retrying: attempt 3 out of 3
// Failure ⛔️
//   Retrying: attempt 1 out of 3
//   Retrying: attempt 2 out of 3
// Success ✅
// Success ✅
//   Retrying: attempt 1 out of 3
// Success ✅
// Success ✅
      

There are multiple reasons why the Ruby version ends up being more concise here, but notably, the retry keyword allows us to avoid the explicit loop structure which would be required without it.

Although it's somewhat rare to use retry in practice, I love it for how directly it expresses the intent whenever it is applicable.

History

The retry functionality was added together with the rest of Ruby's exception handling in version 1.6.0, released in 2000.

The likely inspiration for this feature was, as is often the case, Smalltalk, which had error objects which often could respond to a retry message to indicate that the operation should be retried.

I'm not aware of any other programming languages with built-in retry behavior. Even Crystal opted to not include it, despite being heavily inspired by Ruby.