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.
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 class | Typical response |
|---|---|
| Invalid input or unsupported schema | Correct the request or report a validation error |
| Authentication or authorization failure | Fix credentials or deny access |
| Temporary unavailability | Consider bounded retry if safe |
| Rate limit | Follow documented retry guidance within the deadline |
| Unknown write outcome | Reconcile or use a valid idempotency contract |
| Partial streamed answer | Apply the defined restart or resume policy |
| Refusal or policy outcome | Handle 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.
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 state | Remaining time | Expected decision | Why |
|---|---|---|---|
| Transient, safe, one completed attempt | 2,000 ms | ? | ? |
| Transient, safe, two completed attempts | 2,000 ms | ? | ? |
| Transient, safe, three completed attempts | 2,000 ms | ? | ? |
| Permanent failure, safe replay | 2,000 ms | ? | ? |
| Transient but unsafe replay | 2,000 ms | ? | ? |
| Transient, safe, retry-after 700 ms, one completed attempt | 2,000 ms | ? | ? |
| Same retry-after case | 900 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
- AWS: Timeouts, retries, and backoff with jitter Explains bounded recovery, full jitter, and retry amplification.
- AWS SDKs and Tools: Retry behavior Shows why SDK retry settings belong in the application's retry plan.
- MDN: Retry-After header Covers the delay-in-seconds and HTTP-date forms that must be parsed before the policy sees a delay.
- MDN: AbortController Cancelling in-flight requests and waits in JavaScript.
- Google SRE Workbook: Implementing SLOs Introduces service-level indicators, objectives, and error budgets.
- Google SRE Workbook: Canarying releases Discusses evaluating limited releases before wider exposure.
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.
Related on CodeSnatch
Lessons, problems, and articles that build the skills this question uses.
Code snippet
A sleeping retry loop with full jitter and a retry predicate, useful to contrast with the pure decision function here.
Code snippet
Shows that a Promise.race timeout stops the wait but not the work, and how an AbortSignal cancels the request.
System Design
A retry table by failure type that skips 4xx errors, honors Retry-After on 429, and moves exhausted jobs to a dead-letter queue.
AI Engineering
Explains what Stop cancels and how to label partial output, the stream contract this question asks you to define.
AI Engineering
The idempotency and reconciliation design this question defers to when a timed-out write may have succeeded.
Practice the interview question
Answer it in your own words before you look at the reference answer.
