Start by clarifying the requirements and constraints, then define a clean, generic public API that separates policy (retry logic) from mechanism (network operation). Walk through the default behavior, customization points, and edge cases, emphasizing trade-offs and production readiness.
Pro tip: Show maturity by discussing how to make retries safe for non-idempotent operations and how to avoid retry storms with jitter and circuit breakers. Mention observability hooks (metrics, logging) early, as they are critical for debugging in production.
Ask about the expected use cases, failure types, latency budgets, and whether operations are idempotent. This ensures the design meets real needs and avoids over-engineering.
Design a generic interface (e.g., Retryer<T> with a call method) that accepts a function to execute and configuration options. Keep it simple and composable, allowing callers to wrap any network operation.
Describe sensible defaults: e.g., 3 retries, exponential backoff with jitter, retry on transient errors (timeouts, 5xx), and a maximum elapsed time. Explain why these defaults balance resilience and resource usage.
Detail how callers can override defaults: max attempts, backoff strategy (fixed, exponential, custom), retry condition (predicate on exception/result), timeout per attempt, and hooks for logging/metrics.
Cover important edge cases: non-idempotent operations (require idempotency keys), retry storms (use jitter and circuit breakers), cancellation, thread safety, and resource cleanup. Highlight trade-offs between simplicity and flexibility.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.