Blog
Three problems people might mean by "jitter
Confused by jitter? Learn the difference between retry jitter, latency jitter, and network jitter, plus exponential backoff, retry storms, p99 latency, and coordinated omission.
Blog
Confused by jitter? Learn the difference between retry jitter, latency jitter, and network jitter, plus exponential backoff, retry storms, p99 latency, and coordinated omission.
An engineer debugging a video call, tuning a retry loop, and chasing a p99 latency spike will all say they have a jitter problem, but they are actually three unrelated conditions. It’s used in networking, distributed systems, and performance engineering, and it means something different in each place.
Choosing the wrong reading of the word sends you to the wrong literature, and the wrong literature prescribes the wrong fix. Here are the three different meanings and how to tell quickly which one you see.
In distributed systems, jitter most often refers to randomness added on purpose. When a client fails a request and decides to retry, it waits before trying again. Adding jitter means adding a random offset to that wait so clients do not all retry at the same time. Here, randomness is a feature. You are deliberately introducing a small amount of timing noise to make the system's aggregate behavior more predictable, not less.
The mechanism is easy to see. A client that waits exactly 500 milliseconds before every retry is completely deterministic; 10,000 such clients that failed together will retry together, then fail together, then retry together. A client that waits a random duration drawn from a range spreads those attempts across a window. The individual retry becomes less predictable so that the population becomes more orderly.
In resilience engineering and retry design, this is what people think of first. When someone says "add jitter to your backoff," this is what they mean.
The standard advice for retries is exponential backoff: wait one second, then two, then four, doubling the delay after each failure. Backoff is necessary, but on its own it is not enough. A system running pure exponential backoff can still fall over. Here’s why.
Backoff moves the retry later, but it does not break the wave apart. If every client fails at the same time and every client doubles its delay on the same schedule, they remain synchronized; the server keeps seeing the same pulse of load, just arriving a little later each cycle. The problem is that the requests arrive together because they are all following the same rule. Adding capacity doesn’t help.
Randomness decouples the clients. Backoff by itself controls how long clients wait, but jitter controls whether they wait together. Backoff also lowers the instantaneous request rate, opens recovery windows, and reduces repeated contention, but it is not enough. With a bounded number of clients, backoff reduces the total work the system must do, but with an unbounded number of clients, it mainly defers that work rather than eliminating it.
The question to ask is not "how much traffic can I handle" but "are my clients acting independently?" If they are not, backoff tuning will not help. The easiest way to fix it is to introduce randomness into the wait.
Once you accept that retries need randomness, the next question is how much and what kind. There are three common strategies.
Full jitter waits a random time between zero and the current backoff ceiling.
Equal jitter waits half the ceiling plus a random portion of the other half, which guarantees a minimum wait.
Decorrelated jitter increases the time based on the previous wait rather than a fixed schedule, using sleep = min(cap, random_between(base, sleep * 3)), which spreads attempts the widest but requires each client to carry state between retries.
Simulations found that the number of calls a client makes is roughly similar under full and equal jitter and somewhat higher under decorrelated. Full jitter does the least total work, while decorrelated buys the widest spread at the cost of a few extra attempts.
No one strategy is best. These days, guidance defaults to full or decorrelated jitter for most paths. Where the choice does turn on a guaranteed minimum wait is against a hard rate limit. Full jitter can, by chance, retry almost immediately, which is fine against a service that is briefly busy but wasteful against a quota that will reject anything arriving too soon; equal jitter's floor avoids this.
Even then, pay attention to what the server tells you. When a response carries a Retry-After header or a rate-limit reset timestamp, honoring it is more reliable than any client-side delay formula, and jitter belongs on top of that wait rather than in place of it. Several cloud SDKs now fold this logic into adaptive retry modes that adjust client behavior in response to observed throttling.
As a starting point, when you are choosing your own delays, a base in the low hundreds of milliseconds and a cap in the tens of seconds covers most retry paths; the exact numbers matter less than having both a floor where you need one and a ceiling everywhere.
The practical takeaway is that strategy selection follows the failure you are protecting against. Default to full or decorrelated jitter to spread load against elastic backends
Add a minimum-wait floor when a hard rate limit demands one
Respect a server-supplied retry delay when you are given one
Sometimes jitter doesn’t work because the randomness itself isn’t working.
The classic instance is a pseudo-random generator seeded identically across instances. If every client seeds its generator from the same value, such as a fixed constant, or something effectively identical like a default constructor called at the same time across a fleet, clients draw the same "random" sequence and retry together despite the jitter code.
This was reported against the Polly resilience library: decorrelated jitter didn’t spread load because many simultaneous retries constructed their random generators at once and ended up sharing a seed, producing identical delays. Notably, the fix was to route all delays through one thread-safe shared generator, and current Polly versions do this internally. Runtimes and libraries increasingly guard against this specific problem, but only if you use their shared, properly seeded facilities rather than constructing a fresh generator per attempt.
Two related problems make it worse:
Many implementations jitter the retries but not the first request, so a fleet that wakes up together, such as after a deploy, a network partition healing, or a scheduled job firing, still stampedes on the initial attempt before any backoff logic engages.
Cold starts act as accidental jitter; in a serverless environment, staggered start-up times spread the first requests for you, so the missing randomness only shows up once the fleet is warm and synchronized.
The lesson is that adding jitter and verifying jitter are different. Confirm that your clients are drawing independent values by seeding from a per-instance entropy source, and check that the first request is randomized too, not just the retries.
Performance engineers use "jitter" for the variability in latency that comes from the runtime and the environment, such as garbage collection pauses, thread scheduling, IO stalls, and background compaction.
This shows up in latency tails. A service answers most requests in four milliseconds, but during a garbage collection pause or a background stall, delays a few of them for a hundred milliseconds or more. The mean doesn’t change much, but the p99, or the latency your unluckiest 1% of requests experience, jumps sharply.
The sources of these stalls are structural:
A garbage collector reclaiming memory pauses a thread mid-request.
A process that forks to take a background snapshot stalls while the copy is set up.
Background compaction or index maintenance competes for IO and CPU with live queries.
The operating system scheduler holds a runnable thread while it services something else.
Each of these produces a burst of latency that lands on whichever requests happen to be in progress. In a distributed system, the effect compounds because a pause on one node becomes a timeout on the coordinator waiting for it, so a local stall turns into a distributed one.
If your average looks fine and your users are complaining, stop looking at the average. Measure the tail.
Sometimes, latency spikes and does not come back. A cache that suddenly serves requests an order of magnitude slower and stays slow until someone resets the connections is not experiencing random noise; it has settled into a degraded state and is staying there. That non-recovery signifies a bigger problem. Treat sustained spikes as a different and more serious condition than transient ones.
If latency variability shows up in the tail, how you measure the tail decides whether you see it.
The mechanism is coordinated omission. A closed-loop load tester sends a request, waits for the response, then sends the next one. When the system stalls, the tester stalls with it by stopping issuing requests during the time when latency is worst, so the slow period contributes fewer samples. The slow requests that a real stream of users would have generated don’t get made.
Gil Tene's treatment of this problem includes a worked example where a system serving most requests in a millisecond, but freezing for a long stretch, produces a measurement of about one millisecond at the high percentiles when the accurate figure is on the order of a hundred seconds.
Stop letting the test discard slow requests. Measuring open-loop is the best way: Issue requests at a constant arrival rate regardless of whether prior ones have returned, so a stall produces a pile of delayed requests instead of a gap in the data. Where an open-loop measurement is impractical, correction after the fact achieves the same end, such as histogram tools that backfill the missing samples, or load generators built to compensate for coordinated omission; both restore the requests the naive measurement dropped.
The point is not that only one methodology is valid; it is that the omitted requests must be counted somewhere. A benchmark that reports acceptable tail latency may be reporting an artifact, and provisioning against it will leave you short. Before you trust a p99, confirm the test that produced it did not stop sending requests while the system was struggling.
The third meaning of the word is most common. In networking, network jitter is the variation in the arrival timing of data packets, or packet delay variation. When packets leave a source at even intervals but arrive bunched and scattered, that spread is the jitter, and it degrades real-time applications such as voice and video where steady packet arrival time matters more than raw throughput.
This is a legitimate and well-understood field with its own tooling.
Jitter buffers hold incoming packets briefly and release them at a steady cadence to smooth out the variation.
Network monitoring systems and speed test tools report a jitter figure alongside latency and packet loss to help operators judge connection quality.
High jitter here typically points to network congestion, an overloaded link, or packets in a stream taking different routes.
Here’s how that’s different from the other two meanings.
Network performance concerns incoming packets on a wire.
Distributed systems concerns the timing of retries in your code.
Performance engineering reading concerns variability in your runtime.
Network performance is the oldest and most consumer-facing use, because it is tied to every home internet jitter complaint and every VoIP troubleshooting guide. So here’s a quick test. If the jitter you care about is measured by a speed test or fixed by a jitter buffer, you are in the network performance meaning.
Randomness in retries solves correlation, but it does not solve overload. Once a system is beyond its capacity more than momentarily, decorrelating the clients won’t fix it. At that point, jitter hands off to a different set of tools.
The tools that take over address different problems.
A token bucket handles bursts by letting a reserve of allowance build up during quiet periods and drain during spikes, which suits traffic that is spiky but bounded on average.
A leaky bucket instead smooths output to a fixed rate, which protects a fragile downstream writer that cannot tolerate bursts at all.
Circuit breakers stop sending to a dependency that is failing, so clients stop generating load that cannot succeed.
Load shedding and adaptive concurrency go further by dropping or limiting work when the system detects it is saturated.
Each of these does something jitter cannot: it reduces the total offered load rather than merely rearranging its timing.
These smoothing tools point at one more use of the “jitter” term. "Rate jitter" sometimes refers not to retry timing but to variability in the request arrival rate, the burstiness problem that token and leaky buckets address. Retry jitter is randomness you add to decorrelate clients, while rate variability is an input property of the traffic.
Reach for jitter to decorrelate clients, and reach for bucketing, breaking, and shedding when there is too much work.
When you are trying to solve a jitter problem, ask: Did you add it on purpose?
If you added it deliberately through randomness in a retry or backoff path. You have the first meaning: rate jitter and retry storms, and the questions that matter are which strategy you chose and whether your randomness is independent across clients.
If you did not add it and the problem is variability in your response times, you have the second meaning: tail latency, garbage collection, and measurement issues, and the work is to look past the average and to trust only numbers from a test that counted its slow requests.
If you did not add it and it shows up on the network as scattered packet arrival, you have the third meaning: network jitter, jitter buffers, and connection quality, and the standard networking guidance applies.
Get the classification right, and the correct literature, and the correct fix follows.
Stalls that produce tail latency, such as garbage collection, compaction, and storage housekeeping, are not inevitable properties of software; they are consequences of design choices. Systems built for predictable tail latency address these sources: avoiding runtime pauses such as JVM garbage collection, and keeping performance stable as storage fills and the system ages rather than degrading with it.
Once you stop measuring the average and focus on the tail, the question stops being "how fast is it on a good day" and becomes "what happens to the p99 during a GC pause, a background compaction, or a node under load."
Aerospike was built around that question, by treating bounded tail latency and stable behavior under changing conditions as architectural goals. Teams running interaction-critical paths, where one slow dependent operation blows a deadline, choose Aerospike.
Average throughput tells you little about what breaks deadlines. For a closer look at what to measure and how to tell a credible latency claim from a misleading one, see best practices for database benchmarking.
Marc Brooker, "What Is Backoff For?," Marc's Blog, August 11, 2022, https://brooker.co.za/blog/2022/08/11/backoff.html.
Marc Brooker, "Exponential Backoff and Jitter," AWS Architecture Blog, March 4, 2015, updated May 2023, https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/.
George Polevoy, "Decorrelated Jitter Does Not Jitter Well," GitHub Issue #530, App-vNext/Polly, opened November 9, 2018, https://github.com/App-vNext/Polly/issues/530.
Gil Tene, "How NOT to Measure Latency," presented at QCon San Francisco 2015, InfoQ, March 26, 2016, https://www.infoq.com/presentations/latency-response-time/.
For a deeper understanding and more insights, explore these additional resources.
See moreBlog
Blog
Blog
Blog