AI Engineering
/
Production & Reliability
/

Implement a bounded retry policy

Production & Reliability · 2 to 3 hours

Implement a bounded retry policy

Implement a pure, deterministic retry policy with attempt limits, capped jittered backoff, retry-after, and a deadline check, then fit it into an executor that handles cancellation, partial streams, and uncertain writes.

Medium
Hands-on

3 h

Used in interview rounds:

Live Coding (Hard)

Overview

Implement and test a bounded retry decision policy, then integrate it with deadlines, rate limits, and fallbacks. The exercise is deterministic and makes no network calls: it isolates the policy decisions so timing and failure cases can be tested without real sleeps or paid APIs.

Study time: 2 to 3 hours

Interview format: Implementation exercise with deterministic fixtures, then an integration discussion

By the end you can

  • Classify failures by transience and replay safety before deciding to retry
  • Implement a pure retry policy with an attempt limit, capped exponential backoff, full jitter, and retry-after
  • Refuse a retry when the remaining deadline cannot fit the wait plus a useful attempt
  • Fit the policy into a deadline-aware executor with cancellation and one retry budget across layers
  • Define what happens after a partial streamed response, an uncertain write, or a fallback

Prerequisites

Helpful background: Basic TypeScript or JavaScript is expected; the practice problem also accepts Python.

This topic runs the application. Evaluation & Observability measures its quality, and Model Serving & Inference covers the runtime underneath.

Classify failures before retrying

Failure classTypical response
Invalid input or unsupported schemaCorrect the request or report a validation error
Authentication or authorization failureFix credentials or deny access
Temporary unavailabilityConsider bounded retry if safe
Rate limitFollow documented retry guidance within the deadline
Unknown write outcomeReconcile or use a valid idempotency contract
Partial streamed answerApply the defined restart or resume policy
Refusal or policy outcomeHandle as a distinct result

HTTP status alone may be insufficient. A provider-specific error body can distinguish a temporary rate limit from exhausted billing quota. The adapter should normalize errors while retaining the original diagnostic category in protected telemetry.

Define the retry contract

The teaching policy permits at most three total attempts. It retries only explicitly classified transient failures when replay is safe. It uses capped exponential backoff with full jitter and respects a normalized retry-after delay as a minimum wait.

Before scheduling a retry, it requires enough remaining time for the delay plus a configured minimum useful attempt window. This check does not guarantee that the next request will finish. The runtime must still enforce the actual deadline.

The provider's raw Retry-After value may be a duration or date depending on the protocol. Parsing and validation happen before this pure policy function receives a nonnegative delay in milliseconds.

A pure retry policy

The policy is a pure function. The caller passes the failure classification, the number of attempts already made (including the one that just failed), the time left before the task deadline, and a random value for jitter. Nothing inside it sleeps, reads a clock, or generates randomness, so every decision is reproducible.

type Failure = {
  transient: boolean;
  safeToRetry: boolean;
  retryAfterMs?: number;
};
type Policy = {
  maxAttempts: number;
  baseDelayMs: number;
  maxDelayMs: number;
  minimumAttemptMs: number;
};
type Decision =
  | { retry: true; delayMs: number }
  | { retry: false; reason: "unsafe" | "permanent" | "attempts" | "deadline" };

// Throws on a negative or non-finite duration, an attempt count that is not a
// positive integer, a random value outside [0, 1), or a zero minimumAttemptMs.
// Then stops, in order, for unsafe replay, permanent failure, and used-up attempts.
// Otherwise:
//   ceiling = min(maxDelayMs, baseDelayMs * 2 ** (completedAttempts - 1))
//   delayMs = max(floor(random * ceiling), retryAfterMs ?? 0)
// and stops for "deadline" when delayMs + minimumAttemptMs > remainingMs.
declare function decideRetry(
  failure: Failure,
  completedAttempts: number,
  remainingMs: number,
  random: number,
  policy: Policy
): Decision;

const policy: Policy = {
  maxAttempts: 3, baseDelayMs: 200, maxDelayMs: 1000, minimumAttemptMs: 300
};

Code exercise

Bounded retry policy

Implement decideRetry so each fixture returns its expected decision. Validate the inputs, stop for unsafe replay, permanent failure, and used-up attempts in that order, then compute the capped, jittered delay with retry-after as a minimum, and stop when the delay plus the minimum attempt window does not fit in the remaining time.

Expected

Every fixture in the next exercise returns its expected decision. The reference programs log the decisions after one, two, and three failed attempts with random 0.5, which match the first three rows.

Open coding workspace

Limitations

The function accepts normalized, trusted policy values and an already-classified failure, so validate raw provider responses and external configuration before calling it. It does not sleep, dispatch requests, cancel operations, or reconcile writes. Passing its deadline check does not guarantee that the next attempt finishes: the runtime must still enforce the actual deadline.

Predict the fixture decisions

Predict the outcome

Predict the fixture decisions

Every case uses the policy above (maxAttempts 3, baseDelayMs 200, maxDelayMs 1000, minimumAttemptMs 300) and random value 0.5. Predict each decision before you reveal it.

Failure and stateRemaining timeExpected decisionWhy
Transient, safe, one completed attempt2,000 ms??
Transient, safe, two completed attempts2,000 ms??
Transient, safe, three completed attempts2,000 ms??
Permanent failure, safe replay2,000 ms??
Transient but unsafe replay2,000 ms??
Transient, safe, retry-after 700 ms, one completed attempt2,000 ms??
Same retry-after case900 ms??
Transient, safe, one completed attempt (the first retry needs 100 + 300 ms)399 ms??

Integrate with an asynchronous executor

Maintain one monotonic deadline for the logical task. Before every attempt, compute remaining time and cap dependency timeouts to it. After a failure, classify the error, ask the policy for a decision, and use a cancellation-aware wait.

Recheck the deadline after waiting because scheduling delays can consume more time than planned. Propagate cancellation to the client where supported, and stop dispatching new attempts once the user cancels.

A Promise.race timeout can stop local waiting without aborting remote processing. If a retry starts while the earlier call is still active, resource use can overlap. Track active work and avoid assuming timed-out attempts are free or cancelled.

Coordinate retries across layers

Inspect SDK and proxy behavior. If the HTTP client performs retries internally, its attempts must fit inside the logical task's budget and deadline. Avoid three independent layers each interpreting maxAttempts as its own allowance.

Record both logical request count and physical attempt count. An apparent stable user-request rate can conceal a growing number of provider calls. Measure retry success as well as retry volume so ineffective recovery can be reduced.

For writes or tools with side effects, retain a stable operation identity and apply the idempotency or reconciliation design from Tool Calling & Agents. Do not infer safe replay from a function name.

Handle streaming failures explicitly

Before any output is shown, a safe retry can replace the attempted response. After partial text is displayed, silently appending a newly generated answer may duplicate or contradict earlier content.

Define a UI contract for partial failure. It may preserve the partial response with an interruption label and offer a restart. True resumption requires protocol or application support and cannot be assumed from a new generation call.

Do not replay completed tool effects while restarting a response. Persist operation outcomes and feed their verified results into the resumed conversation.

Design evaluated fallbacks

A fallback can use another model, return source links, or defer work to an asynchronous job. Check output contract, data policy, tool support, quality, and available capacity before routing.

Fallback attempts consume the same logical deadline and cost budget unless the user explicitly begins a new task. Switching providers does not justify resetting the retry counter indefinitely.

When the primary provider is overloaded, sending all traffic immediately to a smaller secondary provider can overload it too. Use admission control and capacity limits for the fallback path. Report when degraded output is returned and evaluate it separately.

Admission control and queues

Limit concurrent expensive calls and bound waiting time. Reject or defer work before consuming substantial resources when the system cannot plausibly finish it within the task deadline.

A semaphore limits in-flight work but does not implement token-per-minute quotas by itself. A token budget or rate limiter addresses a different constraint. Multi-instance deployments need coordinated limits or deliberately partitioned capacity.

Use fair scheduling for tenants or workload classes where required. Large batch work should not consume all capacity needed for interactive requests.

Test failure paths without live outages

Inject a sequence of fake outcomes and a controllable clock into the executor. Verify exact attempt counts, chosen delays, deadline exhaustion, cancellation, fallback eligibility, and partial-stream behavior.

For the pure policy, test invalid durations, random values outside [0,1), zero attempt counts, and a retry-after longer than the remaining deadline. Use fixed random values for deterministic assertions and separate tests for the distribution if needed.

Add integration tests that confirm the client actually receives cancellation and timeout settings. A policy function passing its unit checks does not prove the network stack honors them.

Completion checklist

  • Make all eight fixture decisions pass in the practice problem.
  • Explain exactly when a new attempt is dispatched, denied, cancelled, or routed to a fallback.
  • Show how SDK and proxy retries fit inside one logical task's attempt budget and deadline.
  • Describe the UI contract after a partial streamed response.
  • Explain why an uncertain write needs idempotency or reconciliation before any retry.

Sources

The policy values, fixtures, implementation exercise, and rubric are original synthetic teaching material. The code demonstrates policy decisions rather than a complete network client. Study times and readiness thresholds are editorial estimates. Retry defaults, rate-limit responses, and Retry-After formats differ between providers and SDKs, so check the selected service's current documentation.

Practice the interview question

Answer it in your own words before you look at the reference answer.