Skip to content

Retry Policies — Complete Design and Implementation Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Retry Policies. We cover key concepts, practical examples, and best practices to help you master this topic.

Retry policies define when and how to retry operations through configurable rules for maximum attempts, backoff Strategy, error classification, and total time budgets.

What You'll Learn

By the end of this tutorial, you will design retry policies for different operation types, compose policies, set retry budgets, and centralize retry configuration.

Why It Matters

Different operations need different retry behavior. A payment retry differs from a file read retry. Policies keep retry configuration organized and consistent.

Real-World Use

DodaTech defines retry policies per service in a configuration file. The database service uses one policy, the HTTP client uses another, and message processing uses a third.

Retry Policy Learning Path

flowchart LR
  A[Messaging Retry] --> B[Retry Policies]
  B --> C[Policy Design]
  C --> D[Policy Composition]
  B --> E{You Are Here}
  style E fill:#f90,color:#fff

Defining a Retry Policy

A retry policy is a configuration object that specifies all aspects of retry behavior for a specific operation type.

class RetryPolicy {
  constructor(config) {
    this.name = config.name;
    this.maxAttempts = config.maxAttempts || 3;
    this.baseDelay = config.baseDelay || 200;
    this.maxDelay = config.maxDelay || 30000;
    this.strategy = config.strategy || "exponential";
    this.timeout = config.timeout || 30000;
    this.retryableErrors = config.retryableErrors || [];
    this.jitter = config.jitter || "none";
  }

  getDelay(attempt) {
    let delay;

    switch (this.strategy) {
      case "fixed":
        delay = this.baseDelay;
        break;
      case "incremental":
        delay = this.baseDelay + (attempt * this.baseDelay);
        break;
      case "exponential":
      default:
        delay = this.baseDelay * Math.pow(2, attempt);
        break;
    }

    delay = Math.min(delay, this.maxDelay);

    if (this.jitter === "full") {
      delay = Math.random() * delay;
    } else if (this.jitter === "equal") {
      delay = (delay / 2) + (Math.random() * (delay / 2));
    }

    return Math.floor(delay);
  }

  isRetryable(error) {
    if (this.retryableErrors.length === 0) return true;
    return this.retryableErrors.some(pattern => {
      if (typeof pattern === "string") {
        return error.message.includes(pattern) || error.code === pattern;
      }
      if (pattern instanceof RegExp) {
        return pattern.test(error.message);
      }
      return false;
    });
  }
}

// Usage
const httpPolicy = new RetryPolicy({
  name: "http-api",
  maxAttempts: 3,
  baseDelay: 500,
  maxDelay: 15000,
  strategy: "exponential",
  jitter: "full",
  timeout: 10000,
  retryableErrors: ["ETIMEDOUT", "ECONNREFUSED", /^5\d{2}$/]
});

Policy Registry

A centralized registry stores all retry policies and makes them accessible throughout the application.

class RetryPolicyRegistry {
  constructor() {
    this.policies = new Map();
  }

  register(policy) {
    this.policies.set(policy.name, policy);
  }

  get(name) {
    const policy = this.policies.get(name);
    if (!policy) throw new Error(`Unknown policy: ${name}`);
    return policy;
  }

  async execute(policyName, fn) {
    const policy = this.get(policyName);
    const start = Date.now();

    for (let attempt = 0; attempt < policy.maxAttempts; attempt++) {
      if (Date.now() - start > policy.timeout) {
        throw new Error(`Policy ${policyName} timed out after ${policy.timeout}ms`);
      }

      try {
        return await fn();
      } catch (err) {
        if (!policy.isRetryable(err)) throw err;
        if (attempt === policy.maxAttempts - 1) throw err;

        const delay = policy.getDelay(attempt);
        await new Promise(r => setTimeout(r, delay));
      }
    }
  }
}

// Setup
const registry = new RetryPolicyRegistry();

registry.register(new RetryPolicy({
  name: "db-query",
  maxAttempts: 3,
  baseDelay: 100,
  strategy: "exponential",
  jitter: "equal",
  retryableErrors: ["40001", "40P01"]
}));

registry.register(new RetryPolicy({
  name: "http-api",
  maxAttempts: 5,
  baseDelay: 200,
  maxDelay: 30000,
  strategy: "exponential",
  jitter: "full",
  timeout: 60000,
  retryableErrors: ["ECONNRESET", "ETIMEDOUT"]
}));

// Usage
const result = await registry.execute("http-api", () => fetchData());

Policy Composition

Complex operations may need different policies for different phases. Compose policies for multi-step operations.

class ComposedPolicy {
  constructor(phases) {
    this.phases = phases;
  }

  async execute(context) {
    for (const phase of this.phases) {
      context.currentPhase = phase.name;
      await phase.policy.execute(phase.name, () => phase.action(context));
    }
  }
}

// Example: file upload with two phases
const uploadPolicy = new ComposedPolicy([
  {
    name: "upload-file",
    policy: new RetryPolicy({
      name: "upload",
      maxAttempts: 3,
      baseDelay: 1000,
      maxDelay: 10000,
      strategy: "exponential",
      retryableErrors: ["ETIMEDOUT"]
    }),
    action: async (ctx) => { await uploadToStorage(ctx.file); }
  },
  {
    name: "notify-success",
    policy: new RetryPolicy({
      name: "notify",
      maxAttempts: 5,
      baseDelay: 200,
      strategy: "exponential",
      jitter: "full",
      retryableErrors: [/^5/]
    }),
    action: async (ctx) => { await sendWebhook(ctx.file.id); }
  }
]);

Common Mistakes

  1. One-size-fits-all policy -- Using the same retry configuration for all operations. Different operations need different policies.

  2. Not documenting policy behavior -- Developers need to know what retry behavior to expect. Document each policy's configuration.

  3. Policies that are too permissive -- Retrying forever or retrying non-retryable errors wastes resources.

  4. Policies that are too strict -- Not retrying enough causes unnecessary failures. Balance retry count with user experience.

  5. Hardcoding policy values -- Policies should be configurable without code changes. Use environment variables or config files.

Practice Questions

  1. What should a retry policy configuration include? Max attempts, base delay, max delay, backoff strategy, jitter type, timeout, and retryable error patterns.

  2. Why use a policy registry instead of inline configuration? Centralization ensures consistency, makes policies discoverable, and enables easy configuration changes.

  3. How do you compose policies for multi-step operations? Define phases, each with its own policy. Execute phases sequentially. Different phases may have different retry needs.

  4. Challenge: Design a retry policy that adapts based on time of day.

function getAdaptivePolicy() {
  const hour = new Date().getHours();
  if (hour >= 2 && hour <= 5) {
    return new RetryPolicy({ maxAttempts: 5, baseDelay: 1000 });
  }
  return new RetryPolicy({ maxAttempts: 3, baseDelay: 200 });
}

FAQ

How many retry policies should a typical application have?

3-5: one for database operations, one for HTTP calls, one for message processing, and optionally one for file operations.

Should retry policies be stored in a database?

For small applications, configuration files work. For large systems, a distributed config store allows dynamic policy updates.

How do I version retry policies?

Include a version field in the policy. When making breaking changes, create a new policy version.

Can retry policies be changed at runtime?

Yes. Store policies in a config system that supports hot reload. The registry reads updated policies on change.

How do I test retry policies?

Create unit tests for each policy's delay calculation and error matching. Integration tests verify end-to-end behavior.

Mini Project

Build a complete retry policy system with a registry, multiple policies, error classification, and execution engine.

const policies = {
  "db-read": {
    maxAttempts: 3,
    baseDelay: 50,
    maxDelay: 1000,
    strategy: "exponential",
    retryable: ["40001", "40P01", "08006"]
  },
  "db-write": {
    maxAttempts: 5,
    baseDelay: 100,
    maxDelay: 5000,
    strategy: "exponential",
    jitter: "equal",
    retryable: ["40001", "40P01"]
  },
  "http-get": {
    maxAttempts: 3,
    baseDelay: 200,
    maxDelay: 10000,
    strategy: "exponential",
    jitter: "full",
    totalTimeout: 30000,
    retryable: ["ECONNRESET", "ETIMEDOUT", "429", "5xx"]
  },
  "http-post": {
    maxAttempts: 2,
    baseDelay: 500,
    maxDelay: 5000,
    strategy: "fixed",
    retryable: ["ECONNRESET", "ETIMEDOUT"]
  },
  "queue-processing": {
    maxAttempts: 5,
    baseDelay: 1000,
    maxDelay: 60000,
    strategy: "exponential",
    jitter: "full",
    totalTimeout: 300000,
    retryable: ["timeout", "connection"]
  }
};

class PolicyEngine {
  constructor(policies) {
    this.policies = policies;
  }

  async execute(policyName, fn) {
    const policy = this.policies[policyName];
    const start = Date.now();

    for (let attempt = 0; attempt < policy.maxAttempts; attempt++) {
      try {
        return await fn();
      } catch (err) {
        const elapsed = Date.now() - start;
        if (elapsed > (policy.totalTimeout || 60000)) {
          throw new Error(`Policy ${policyName} total timeout exceeded`);
        }
        if (attempt === policy.maxAttempts - 1) throw err;
        if (!this.isRetryable(err, policy)) throw err;

        const delay = this.getDelay(policy, attempt);
        await new Promise(r => setTimeout(r, delay));
      }
    }
  }

  isRetryable(err, policy) {
    return policy.retryable.some(p => err.code === p || err.message.includes(p));
  }

  getDelay(policy, attempt) {
    let delay = policy.baseDelay * Math.pow(2, attempt);
    delay = Math.min(delay, policy.maxDelay);
    if (policy.jitter === "full") delay = Math.random() * delay;
    if (policy.jitter === "equal") delay = (delay / 2) + Math.random() * (delay / 2);
    return Math.floor(delay);
  }
}

What's Next

Now that you understand retry policies, explore testing retry logic. Then learn about monitoring retry performance.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro