Coding problem · Medium · Language Idioms

Decorator with Arguments: @retry

Medium Language practicals Seen at Dropbox, Stripe, Reddit

Implement a decorator factory retry(times, exceptions):

@retry(times=3, exceptions=(TimeoutError,))
def fetch(url): ...
  • Call the function up to times times while it raises one of exceptions; return the first successful result.
  • After the last failed attempt, re-raise that exception.
  • Exceptions not listed propagate immediately.
  • The decorated function keeps its __name__ and __doc__, and passes through all arguments.

Hints

Hint 1

Three levels: retry(...) returns decorator(func), which returns wrapper(*args, **kwargs).

Hint 2

functools.wraps(func) copies the metadata.

Write and run your solution

Loading the editor… (JavaScript is required to run code in your browser.)

Solution and explanation

Show the solution

Time O(times) calls · Space O(1)

A decorator with arguments is a function that returns a decorator: retry(times, exceptions) captures the configuration, decorator(func) captures the function, and wrapper(*args, **kwargs) runs the retry loop. except exceptions: accepts a tuple, so only listed types are caught; a bare raise re-raises the current exception with its traceback. functools.wraps keeps __name__, __doc__ and __wrapped__, which matters for logging, debugging and frameworks that inspect functions.

Reference solution

import functools
from typing import Callable


def retry(times: int, exceptions: tuple[type[BaseException], ...]) -> Callable:
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions:
                    if attempt == times:
                        raise
        return wrapper
    return decorator

Common mistakes

  • Forgetting functools.wraps (all routes named wrapper).
  • Catching Exception regardless of the configured types.
  • raise e instead of bare raise in older code loses context.

Follow-up questions

  • Add exponential backoff with an injectable sleep.
  • Make it work for async def functions too.