The Retry Pattern
Fast recall
Quick revision
Core answer
retry can be used inside a rescue clause to run the associated begin body again from its start. A safe retry must be limited, target only transient failures, delay between attempts, and re-raise once the limit is reached. Because the whole operation repeats, it should also be idempotent—or protected by an idempotency key—so a partial first attempt cannot duplicate a payment, message, or write.Whether you know retry re-runs the begin block, must be guarded by a counter, and uses backoff; and that it's for transient errors only.
Unguarded retry (infinite loop) or retrying a deterministic bug (it fails the same way forever).
retry re-runs the begin block; guard with a counter + backoff; re-raise after N tries. For transient failures only, not bugs.
Interview-ready guide
Interview answer
Ruby allows retry inside a rescue clause. It sends execution back to the start of the associated begin body, so every statement in that protected operation runs again—not only the line that raised. Attempt counters and any state changed before the failure therefore need to be placed and designed deliberately.
Retry a narrow transient condition such as a short timeout or connection reset, where the environment may recover while the request stays valid. Invalid credentials, malformed input, and programming errors will fail again with the same inputs and should return immediately. Rescue only the explicit transient classes that the operation's contract knows how to repeat.
Count attempts, re-raise when the maximum is reached, and wait before another try. Exponential backoff increases the delay after consecutive failures; jitter is often added in production so many clients do not retry together. The runnable client fails twice, succeeds on the third call, and records 0.25 and 0.5 second policies without making the lesson actually sleep.
A read is often safe to repeat, while a payment, email, inventory decrement, or published job may already have succeeded before the client saw a timeout. Protect those operations with an idempotency key, deduplication record, or status check. After the final attempt, bare raise preserves the last exception and backtrace; metrics should record attempts without multiplying the same stack trace at every layer.
The Retry Pattern
Direct Answer
Ruby's retry can be used inside a rescue clause to run the associated begin body again from its start. A safe retry must be limited, target only transient failures, delay between attempts, and re-raise once the limit is reached. Because the whole operation repeats, it should also be idempotent—or protected by an idempotency key—so a partial first attempt cannot duplicate a payment, message, or write.
Deep Dive & Architecture
A failure must be classified before it is retried. Transient means the environment may change between attempts even though the request stays valid. Permanent means the same request will predictably fail again. This classification is more important than the syntax because retrying a permanent error only adds delay and noise.
Backoff gives the dependency time to recover and prevents every client from issuing another request at once. Exponential backoff increases the delay after consecutive failures; random jitter spreads clients that otherwise share the same schedule. The total attempts and elapsed time should still fit the caller's timeout budget.
Repeating the whole begin block creates a transaction boundary. Any mutation before the failed step may happen twice. Reads and naturally idempotent updates are usually safe; payments, emails, inventory decrements, and job publication need a unique request key, a deduplication record, or a status check.
The final failure remains meaningful. After the attempt limit, bare raise preserves the last exception and its backtrace for the caller. Metrics should record attempts and final outcome without logging the same stack trace at every layer.
Code Example
require 'timeout'
def fetch_with_retry(client, max_attempts: 3)
attempts = 0
begin
attempts += 1
client.fetch
rescue Timeout::Error, Errno::ECONNRESET => error
raise if attempts >= max_attempts
delay = 0.25 * (2 ** (attempts - 1))
warn "#{error.class}; retrying in #{delay}s"
sleep(delay)
retry
end
end
Common Mistakes & Pitfalls
Unguarded retry (infinite loop) or retrying a deterministic bug (it fails the same way forever).
Check your understanding
Answer these without looking back at the article.
- What part of the code does Ruby run again after `retry`?
- Which failures are transient, and which should fail immediately?
- Why does retrying a write require idempotency protection?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.