# Strategies Explained¶

## Why Are Backoff Strategies Useful?¶

Because now and again, stuff breaks.

Often, when making function calls, something goes wrong. The internet might glitch. The API we’re calling might timeout. Gremlins might eat your packets. Any number of things can go wrong, and Murphy’s law tells us that they will.

Which is why we need backoff strategies. Basically, a backoff strategy is a technique that we can use to retry failing function calls after a given delay - and keep retrying them until either the function call works, or until we’ve tried so many times that we just give up and handle the error.

## How Do Strategies Work?¶

In the **Backoff-Utils** library, strategies exist to calculate the delay that
should be applied between retries. That’s all they do. Everything else is
handled by the `backoff()`

function and
`@apply_backoff`

decorator.

The library supports five different strategies, each of which inherits from
`BackoffStrategy`

.

Caution

`BackoffStrategy`

is itself
an abstract base class and cannot be instantiated directly. You can subclass
it to create your own custom strategies, or you can supply one of our ready-made
strategies as the `strategy`

argument when applying a backoff.

When you apply a backoff strategy, you must supply a `strategy`

argument which
can accept either a class that inherits from
`BackoffStrategy`

, or
an *instance* of a class that inherits from
`BackoffStrategy`

.

Passing a class will use the default configuration for the backoff strategy, while passing an instance will let you modify that configuration. For example:

```
result = backoff(some_function,
args = ['value1', 'value2'],
kwargs = { 'kwarg1': 'value3' },
max_tries = 3,
max_delay = 30,
strategy = strategies.Exponential)
```

will call `some_function()`

with an
`Exponential`

strategy applying its
default settings, while:

```
my_strategy = strategies.Polynomial(exponent = 3, scale_factor = 0.5)
result = backoff(some_function,
args = ['value1', 'value2'],
kwargs = { 'kwarg1': 'value3' },
max_tries = 3,
max_delay = 30,
strategy = my_strategy)
```

will call `some_function()`

with a
`Polynomial`

strategy using an
exponent of 3 and a scale factor of 0.5.

## Strategy Features¶

### Random Jitter¶

All strategies support using a random jitter.

You can deactivate the jitter on a strategy by instantiating it with the argument
`jitter = False`

. For example:

```
my_strategy = strategies.Exponential(jitter = False)
```

will ensure that no jitter is applied.

Hint

By default, all strategies apply a random jitter unless explicitly deactivated.

### Minimum Delay¶

While each strategy calculates its delay based on its own logic, you can ensure
that the delay returned is always a certain minimum number of seconds. You can apply a
minimum by instantiating a strategy with the `minimum`

argument. For example:

```
my_strategy = strategies.Exponential(minimum = 5)
```

will ensure that at least 5 seconds will pass between retry attempts.

Hint

By default, there is no minimum.

### Scale Factor¶

Certain strategies - like the `Polynomial`

strategy - can rapidly lead to very long delays between retry attempts. To offset
this, while still retaining the shape of the curve between retry attempts, each
strategy has a
`scale_factor`

property
which is multipled by the “unadjusted” delay. This can be used to reduce (or increase)
the size (technically the magnitude) of the delay.

To apply a scale factor, pass it as the `scale_factor`

argument when
instantiating the strategy. For example:

```
my_strategy = strategies.Exponential(scale_factor = 0.5)
```

will ensure that whatever delay is calculated will always be reduced by 50% before being applied.

Hint

The scale factor defaults to a value of `1.0`

.

## Supported Strategies¶

The library comes with five commonly-used backoff/retry strategies:

However, you can also create your own custom strategies
by inheriting from `BackoffStrategy`

.

### Exponential¶

The base delay time is calculated as:

where \(a\) is the number of unsuccessful attempts that have been made.

### Fibonacci¶

The base delay time is returned as the Fibonacci number corresponding to the current attempt.

### Fixed¶

The base delay time is calculated as a fixed value determined by the attempt number.

To configure the sequence, instantiate the strategy passing an iterable to
`sequence`

like in the example below:

```
my_strategy = strategies.Fixed(sequence = [2, 4, 6, 8])
```

Note

If the number of attempts exceeds the length of the sequence, the last delay in the sequence will be repeated.

Tip

If no sequence is given, by default each base delay will be 1 second long.

### Polynomial¶

The base delay time is calculated as:

where:

- \(a\) is the number of unsuccessful attempts that have been made,
- \(e\) is the
`exponent`

configured for the strategy.

To set the exponent, pass `exponent`

as an argument to the class as follows:

```
my_strategy = strategies.Polynomial(exponent = 2)
```

will calculate the base delay as

where \(a\) is the number of unsuccessful attempts that have been made.

## Creating Your Own Strategies¶

You can create your own custom backoff strategy by subclassing from
`strategies.BackoffStrategy`

.
When you do so, you will need to define your own `time_to_sleep`

property which
returns a `float`

.

For example:

```
import random
from backoff_utils import strategies
class MyCustomStrategy(strategies.BackoffStrategy):
"""This is a custom strategy that will always wait a random number of
milliseconds."""
@property
def time_to_sleep(self):
return random.random()
```

The custom strategy created above will always wait a random number of milliseconds, regardless of anything else. You can make your classes as complicated as they need to be, and use whatever logic you choose.