Back to the library
Query plan, early exit
Variant: Early exit
The same scan at the same 280ms cadence, but the query has a LIMIT of three. The result set fills at row five, its outline snaps closed, and the cursor stops dead instead of walking off the end; the five rows below the cut drop to 45% because they are never read. The halt is the whole animation — a scan is not slow, finishing one is.
diagramdatabasesvg
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>Query plan — early exit</title>
<style>
:root {
--ivory: #faf9f5;
--slate: #141413;
--clay: #d97757;
--oat: #e3dacc;
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);
--cycle: 3s;
}
* {
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);
}
svg {
width: 280px;
max-width: 100%;
height: auto;
display: block;
}
.row {
fill: var(--oat);
}
.read,
.hit {
fill: var(--clay);
opacity: 0;
}
/* Same 280ms-per-row cadence as the full scan — this plan is not faster
per row, it simply stops sooner. Keeping the pitch identical is what
makes the two readable side by side. */
.read {
animation: read-flash var(--cycle) linear infinite;
}
.read:nth-child(1) {
animation-delay: 0.3s;
}
.read:nth-child(2) {
animation-delay: 0.58s;
}
.read:nth-child(3) {
animation-delay: 0.86s;
}
.read:nth-child(4) {
animation-delay: 1.14s;
}
.read:nth-child(5) {
animation-delay: 1.42s;
}
@keyframes read-flash {
0% {
opacity: 0;
animation-timing-function: var(--ease-out);
}
4.1% {
opacity: 0.55;
}
22.7%,
100% {
opacity: 0;
}
}
.hit-a {
animation: hit-a var(--cycle) linear infinite;
}
.hit-b {
animation: hit-b var(--cycle) linear infinite;
}
.hit-c {
animation: hit-c var(--cycle) linear infinite;
}
@keyframes hit-a {
0%,
14.1% {
opacity: 0;
animation-timing-function: var(--ease-out);
}
18%,
88.3% {
opacity: 1;
}
95%,
100% {
opacity: 0;
}
}
@keyframes hit-b {
0%,
32.7% {
opacity: 0;
animation-timing-function: var(--ease-out);
}
36.6%,
88.3% {
opacity: 1;
}
95%,
100% {
opacity: 0;
}
}
@keyframes hit-c {
0%,
51.3% {
opacity: 0;
animation-timing-function: var(--ease-out);
}
55.4%,
88.3% {
opacity: 1;
}
95%,
100% {
opacity: 0;
}
}
/* The five rows below the cut are never read. Dropping them to 45% after
the halt is the payoff: the plan bought its speed by not looking. */
.unread {
animation: unread-dim var(--cycle) linear infinite;
}
@keyframes unread-dim {
0%,
66% {
opacity: 1;
animation-timing-function: var(--ease-out);
}
74.6%,
88.3% {
opacity: 0.45;
}
95%,
100% {
opacity: 1;
}
}
.probe path {
fill: var(--clay);
}
.probe-fade {
animation: probe-fade var(--cycle) linear infinite;
}
@keyframes probe-fade {
0% {
opacity: 0;
animation-timing-function: var(--ease-out);
}
10%,
88.3% {
opacity: 1;
}
95%,
100% {
opacity: 0;
}
}
/* steps(5, jump-none) lands the cursor on rows 1 through 5 and leaves it
parked on the fifth. It does not walk off the end — that stop is the
whole animation. */
.probe-step {
animation: probe-walk var(--cycle) linear infinite;
}
@keyframes probe-walk {
0%,
10% {
transform: translateY(0);
animation-timing-function: steps(5, jump-none);
}
47.4%,
100% {
transform: translateY(56px);
}
}
.stop-bar {
fill: var(--clay);
opacity: 0;
transform-box: fill-box;
transform-origin: left center;
animation: stop-in var(--cycle) linear infinite;
}
@keyframes stop-in {
0%,
63.3% {
opacity: 0;
transform: scaleX(0.9);
animation-timing-function: var(--ease-out);
}
69.3%,
88.3% {
opacity: 1;
transform: scaleX(1);
}
95%,
100% {
opacity: 0;
transform: scaleX(1);
}
}
.feed {
stroke: var(--oat);
stroke-width: 1.5;
fill: none;
}
.slot {
fill: none;
stroke: var(--slate);
stroke-width: 1;
stroke-dasharray: 3 3;
opacity: 0.22;
}
.slot-full {
fill: none;
stroke: var(--clay);
stroke-width: 1.5;
opacity: 0;
animation: slot-full var(--cycle) linear infinite;
}
@keyframes slot-full {
0%,
61.3% {
opacity: 0;
animation-timing-function: var(--ease-out);
}
66.6%,
88.3% {
opacity: 1;
}
95%,
100% {
opacity: 0;
}
}
.chip {
fill: var(--clay);
opacity: 0;
transform-box: fill-box;
transform-origin: center;
}
.chip-a {
animation: chip-a var(--cycle) linear infinite;
}
.chip-b {
animation: chip-b var(--cycle) linear infinite;
}
.chip-c {
animation: chip-c var(--cycle) linear infinite;
}
@keyframes chip-a {
0%,
15.3% {
opacity: 0;
transform: translateX(-14px) scale(0.96);
animation-timing-function: var(--ease-out);
}
22.7%,
88.3% {
opacity: 1;
transform: translateX(0) scale(1);
}
95%,
100% {
opacity: 0;
transform: translateX(0) scale(0.97);
}
}
@keyframes chip-b {
0%,
33.9% {
opacity: 0;
transform: translateX(-14px) scale(0.96);
animation-timing-function: var(--ease-out);
}
41.3%,
88.3% {
opacity: 1;
transform: translateX(0) scale(1);
}
95%,
100% {
opacity: 0;
transform: translateX(0) scale(0.97);
}
}
@keyframes chip-c {
0%,
52.7% {
opacity: 0;
transform: translateX(-14px) scale(0.96);
animation-timing-function: var(--ease-out);
}
60%,
88.3% {
opacity: 1;
transform: translateX(0) scale(1);
}
95%,
100% {
opacity: 0;
transform: translateX(0) scale(0.97);
}
}
.meter-track {
fill: var(--oat);
}
.meter-tick {
fill: var(--ivory);
}
.meter-fill {
fill: var(--clay);
transform-box: fill-box;
transform-origin: left center;
animation: meter-fill var(--cycle) linear infinite;
}
@keyframes meter-fill {
0%,
10% {
transform: scaleX(0);
opacity: 1;
animation-timing-function: steps(5, jump-start);
}
56.6%,
88.3% {
transform: scaleX(0.5);
opacity: 1;
}
95% {
transform: scaleX(0.5);
opacity: 0;
}
95.2%,
100% {
transform: scaleX(0);
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.probe-fade,
.probe-step {
animation: none;
opacity: 0;
}
.stop-bar {
animation: stop-still var(--cycle) linear infinite;
}
@keyframes stop-still {
0%,
63.3% {
opacity: 0;
transform: scaleX(1);
}
69.3%,
88.3% {
opacity: 1;
transform: scaleX(1);
}
95%,
100% {
opacity: 0;
transform: scaleX(1);
}
}
.chip-a {
animation: chip-still-a var(--cycle) linear infinite;
}
.chip-b {
animation: chip-still-b var(--cycle) linear infinite;
}
.chip-c {
animation: chip-still-c var(--cycle) linear infinite;
}
@keyframes chip-still-a {
0%,
15.3% {
opacity: 0;
}
22.7%,
88.3% {
opacity: 1;
}
95%,
100% {
opacity: 0;
}
}
@keyframes chip-still-b {
0%,
33.9% {
opacity: 0;
}
41.3%,
88.3% {
opacity: 1;
}
95%,
100% {
opacity: 0;
}
}
@keyframes chip-still-c {
0%,
52.7% {
opacity: 0;
}
60%,
88.3% {
opacity: 1;
}
95%,
100% {
opacity: 0;
}
}
/* Cost stated, not travelled: five of ten rows read. */
.meter-fill {
animation: none;
transform: scaleX(0.5);
}
}
</style>
</head>
<body>
<svg
viewBox="0 0 280 200"
role="img"
aria-label="A scan that stops after five rows because the three-row result set is already full"
>
<g>
<rect class="row" x="64" y="24" width="112" height="10" rx="2" />
<rect class="row" x="64" y="38" width="112" height="10" rx="2" />
<rect class="row" x="64" y="52" width="112" height="10" rx="2" />
<rect class="row" x="64" y="66" width="112" height="10" rx="2" />
<rect class="row" x="64" y="80" width="112" height="10" rx="2" />
</g>
<g class="unread">
<rect class="row" x="64" y="94" width="112" height="10" rx="2" />
<rect class="row" x="64" y="108" width="112" height="10" rx="2" />
<rect class="row" x="64" y="122" width="112" height="10" rx="2" />
<rect class="row" x="64" y="136" width="112" height="10" rx="2" />
<rect class="row" x="64" y="150" width="112" height="10" rx="2" />
</g>
<g>
<rect class="read" x="64" y="24" width="112" height="10" rx="2" />
<rect class="read" x="64" y="38" width="112" height="10" rx="2" />
<rect class="read" x="64" y="52" width="112" height="10" rx="2" />
<rect class="read" x="64" y="66" width="112" height="10" rx="2" />
<rect class="read" x="64" y="80" width="112" height="10" rx="2" />
</g>
<rect class="hit hit-a" x="64" y="24" width="112" height="10" rx="2" />
<rect class="hit hit-b" x="64" y="52" width="112" height="10" rx="2" />
<rect class="hit hit-c" x="64" y="80" width="112" height="10" rx="2" />
<rect class="stop-bar" x="58" y="91" width="124" height="2" rx="1" />
<g class="probe">
<g class="probe-fade">
<g class="probe-step">
<path d="M46 24 L58 29 L46 34 Z" />
</g>
</g>
</g>
<path class="feed" d="M180 85 L192 85" />
<rect class="slot" x="194" y="58" width="40" height="54" rx="4" />
<rect class="slot-full" x="194" y="58" width="40" height="54" rx="4" />
<rect class="chip chip-a" x="200" y="66" width="28" height="10" rx="2" />
<rect class="chip chip-b" x="200" y="84" width="28" height="10" rx="2" />
<rect class="chip chip-c" x="200" y="102" width="28" height="10" rx="2" />
<rect class="meter-track" x="64" y="176" width="112" height="5" rx="2.5" />
<rect class="meter-fill" x="64" y="176" width="112" height="5" rx="2.5" />
<g class="meter-tick">
<rect x="75.2" y="176" width="1.6" height="5" />
<rect x="86.4" y="176" width="1.6" height="5" />
<rect x="97.6" y="176" width="1.6" height="5" />
<rect x="108.8" y="176" width="1.6" height="5" />
<rect x="120" y="176" width="1.6" height="5" />
<rect x="131.2" y="176" width="1.6" height="5" />
<rect x="142.4" y="176" width="1.6" height="5" />
<rect x="153.6" y="176" width="1.6" height="5" />
<rect x="164.8" y="176" width="1.6" height="5" />
</g>
</svg>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.