Back to the library
Retry budget exhausted
Variant: Budget exhausted
Backoff doing everything right against an outage that outlasts it. The gaps double exactly as in the exponential variant, but the server is hard-down, so nothing the client does moves its meter. The four pips inside the client are the retry budget; each goes out as its request leaves, and when the last one does the client turns rust and the timeline stops advancing. Use it to document give-up and dead-letter behaviour.
errordiagraminfrastructure
The source
A complete HTML document. Paste it into an empty .html file and it plays — no stylesheet, script, font, or image to fetch.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Retry budget exhausted</title>
<style>
:root {
--ivory: #faf9f5;
--slate: #141413;
--clay: #d97757;
--oat: #e3dacc;
--rust: #b04a3f;
--gray-500: #87867f;
--loop: 5.6s;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
display: grid;
place-items: center;
background: var(--ivory);
font-family:
system-ui,
-apple-system,
'Segoe UI',
Roboto,
sans-serif;
color: var(--slate);
}
.diagram {
width: 260px;
height: 112px;
}
.node {
fill: none;
stroke: var(--oat);
stroke-width: 1.5;
}
.link,
.axis {
stroke: var(--oat);
stroke-width: 1.5;
stroke-linecap: round;
}
.cap {
font-size: 8.5px;
letter-spacing: 0.08em;
fill: var(--gray-500);
text-anchor: middle;
}
.meter-track,
.socket {
fill: var(--oat);
}
/* Identical 40 units-per-second axis and identical 24 / 48 / 96 unit gaps
as the succeeding variant. The backoff is doing its job — the outage is
simply longer than four attempts can cover. */
.progress {
stroke: var(--clay);
stroke-width: 2.5;
stroke-dasharray: 192;
stroke-dashoffset: 192;
animation: sweep var(--loop) linear infinite;
}
.tick {
fill: var(--rust);
transform-box: fill-box;
transform-origin: bottom center;
opacity: 0;
}
.t1 {
animation: t1 var(--loop) linear infinite;
}
.t2 {
animation: t2 var(--loop) linear infinite;
}
.t3 {
animation: t3 var(--loop) linear infinite;
}
.t4 {
animation: t4 var(--loop) linear infinite;
}
.req {
fill: var(--clay);
opacity: 0;
animation: req var(--loop) linear infinite;
}
.rej {
fill: var(--rust);
opacity: 0;
animation: rej var(--loop) linear infinite;
}
/* The server bar is flat and full for the whole loop: this is a hard
outage, not a load problem, so nothing the client does moves it. All
the state that changes here lives on the client side. */
.load {
fill: var(--rust);
transform-box: fill-box;
transform-origin: left center;
transform: scaleX(0.9);
}
.down {
fill: none;
stroke: var(--rust);
stroke-width: 1.5;
}
/* One pip per remaining attempt. Each goes out at the moment its request
leaves, so the budget is visibly spent before the give-up lands. */
.pip {
fill: var(--clay);
}
.p1 {
animation: p1 var(--loop) linear infinite;
}
.p2 {
animation: p2 var(--loop) linear infinite;
}
.p3 {
animation: p3 var(--loop) linear infinite;
}
.p4 {
animation: p4 var(--loop) linear infinite;
}
.giveup {
fill: none;
stroke: var(--rust);
stroke-width: 1.5;
opacity: 0;
transform-box: fill-box;
transform-origin: center;
animation: giveup var(--loop) linear infinite;
}
.live {
animation: live var(--loop) linear infinite;
}
@keyframes live {
0% {
opacity: 0;
}
1.43%,
94.64% {
opacity: 1;
}
99.11%,
100% {
opacity: 0;
}
}
@keyframes sweep {
0% {
stroke-dashoffset: 192;
}
85.71%,
100% {
stroke-dashoffset: 0;
}
}
@keyframes t1 {
0%,
2.68% {
opacity: 0;
transform: scaleY(0.72);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
5.54%,
100% {
opacity: 1;
transform: scaleY(1);
}
}
@keyframes t2 {
0%,
13.39% {
opacity: 0;
transform: scaleY(0.72);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
16.25%,
100% {
opacity: 1;
transform: scaleY(1);
}
}
@keyframes t3 {
0%,
34.82% {
opacity: 0;
transform: scaleY(0.72);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
37.68%,
100% {
opacity: 1;
transform: scaleY(1);
}
}
@keyframes t4 {
0%,
77.68% {
opacity: 0;
transform: scaleY(0.72);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
80.54%,
100% {
opacity: 1;
transform: scaleY(1);
}
}
@keyframes p1 {
0%,
2.68% {
opacity: 1;
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
5.18%,
100% {
opacity: 0;
}
}
@keyframes p2 {
0%,
13.39% {
opacity: 1;
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
15.89%,
100% {
opacity: 0;
}
}
@keyframes p3 {
0%,
34.82% {
opacity: 1;
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
37.32%,
100% {
opacity: 0;
}
}
@keyframes p4 {
0%,
77.68% {
opacity: 1;
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
80.18%,
100% {
opacity: 0;
}
}
/* Flight is linear — constant motion, not a state change. The give-up
frame is 180ms ease-out, landing the instant the last rejection is home. */
@keyframes req {
0%,
2.68% {
transform: translateX(0);
opacity: 0;
}
3.1% {
opacity: 1;
}
6.3% {
opacity: 1;
}
6.61% {
transform: translateX(120px);
opacity: 0;
}
6.62%,
13.39% {
transform: translateX(0);
opacity: 0;
}
13.81% {
opacity: 1;
}
17.1% {
opacity: 1;
}
17.32% {
transform: translateX(120px);
opacity: 0;
}
17.33%,
34.82% {
transform: translateX(0);
opacity: 0;
}
35.24% {
opacity: 1;
}
38.53% {
opacity: 1;
}
38.75% {
transform: translateX(120px);
opacity: 0;
}
38.76%,
77.68% {
transform: translateX(0);
opacity: 0;
}
78.1% {
opacity: 1;
}
81.3% {
opacity: 1;
}
81.61% {
transform: translateX(120px);
opacity: 0;
}
81.62%,
100% {
transform: translateX(0);
opacity: 0;
}
}
@keyframes rej {
0%,
6.61% {
transform: translateX(120px);
opacity: 0;
}
7.03% {
opacity: 1;
}
10.12% {
opacity: 1;
}
10.54% {
transform: translateX(0);
opacity: 0;
}
10.55%,
17.32% {
transform: translateX(120px);
opacity: 0;
}
17.74% {
opacity: 1;
}
20.83% {
opacity: 1;
}
21.25% {
transform: translateX(0);
opacity: 0;
}
21.26%,
38.75% {
transform: translateX(120px);
opacity: 0;
}
39.17% {
opacity: 1;
}
42.26% {
opacity: 1;
}
42.68% {
transform: translateX(0);
opacity: 0;
}
42.69%,
81.61% {
transform: translateX(120px);
opacity: 0;
}
82.03% {
opacity: 1;
}
85.23% {
opacity: 1;
}
85.54% {
transform: translateX(0);
opacity: 0;
}
85.55%,
100% {
transform: translateX(120px);
opacity: 0;
}
}
@keyframes giveup {
0%,
85.54% {
opacity: 0;
transform: scale(0.97);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
88.75%,
100% {
opacity: 1;
transform: scale(1);
}
}
@media (prefers-reduced-motion: reduce) {
.live,
.progress,
.tick,
.req,
.rej,
.pip,
.giveup {
animation: none;
}
/* Reduced motion holds the terminal state — widening ticks, an empty
budget, a dead client. Only the give-up frame breathes. */
.progress {
stroke-dashoffset: 0;
}
.tick {
opacity: 1;
transform: none;
}
.req,
.rej,
.pip {
opacity: 0;
}
.giveup {
opacity: 1;
transform: none;
animation: dead 2.6s ease-in-out infinite;
}
@keyframes dead {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.45;
}
}
}
</style>
</head>
<body>
<svg
class="diagram"
viewBox="0 0 260 112"
role="img"
aria-label="A client retries a hard-down server with exponential backoff, spends its budget of four attempts, and gives up"
>
<g class="structure">
<line class="link" x1="70" y1="29" x2="190" y2="29" />
<rect class="node" x="14" y="12" width="56" height="34" rx="7" />
<rect class="node" x="190" y="12" width="56" height="34" rx="7" />
<rect class="socket" x="23.5" y="36.5" width="7" height="3.5" rx="1.75" />
<rect class="socket" x="33.5" y="36.5" width="7" height="3.5" rx="1.75" />
<rect class="socket" x="43.5" y="36.5" width="7" height="3.5" rx="1.75" />
<rect class="socket" x="53.5" y="36.5" width="7" height="3.5" rx="1.75" />
<rect class="meter-track" x="198" y="36.5" width="40" height="3.5" rx="1.75" />
<line class="axis" x1="14" y1="100" x2="246" y2="100" />
</g>
<g class="live">
<rect class="load" x="198" y="36.5" width="40" height="3.5" rx="1.75" />
<rect class="down" x="190" y="12" width="56" height="34" rx="7" />
<rect class="pip p1" x="23.5" y="36.5" width="7" height="3.5" rx="1.75" />
<rect class="pip p2" x="33.5" y="36.5" width="7" height="3.5" rx="1.75" />
<rect class="pip p3" x="43.5" y="36.5" width="7" height="3.5" rx="1.75" />
<rect class="pip p4" x="53.5" y="36.5" width="7" height="3.5" rx="1.75" />
<rect class="giveup" x="14" y="12" width="56" height="34" rx="7" />
<line class="progress" x1="14" y1="100" x2="206" y2="100" />
<rect class="tick t1" x="18.75" y="86" width="2.5" height="14" />
<rect class="tick t2" x="42.75" y="86" width="2.5" height="14" />
<rect class="tick t3" x="90.75" y="86" width="2.5" height="14" />
<rect class="tick t4" x="186.75" y="82" width="2.5" height="18" />
<circle class="req" cx="70" cy="29" r="4.5" />
<circle class="rej" cx="70" cy="29" r="4.5" />
</g>
<g class="labels">
<text class="cap" x="42" y="29">CLIENT</text>
<text class="cap" x="218" y="29">SERVER</text>
</g>
</svg>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.