The book's two events arrive at 10:03:39 and 10:08:12 — 4m33s apart, so under 5 minutes. Feed that same pair through four operators that all call themselves "5-minute windows" and they are grouped four different ways: sliding holds them together (count 2), tumbling splits them across a grid boundary (count 1 + 1), hopping scatters them into overlapping windows that share none, and session groups them by a 30-minute gap. The size is fixed; the grouping belongs to the operator.
The book's two example events arrive at 10:03:39 and 10:08:12 — 4 minutes 33 seconds apart, so strictly less than 5 minutes. Feed that same pair through four operators that all call themselves "5-minute windows" and they are grouped four different ways:
[10:00, 10:05), 10:08:12 in [10:05, 10:10), count 1 + 1;Same stream, same 5-minute width, different count and different grouping every time. "5 minutes" fixes the window's size but not its boundaries, its overlap rule, or whether boundaries exist at all. The grouping belongs to the operator, not the duration.
Designing Data-Intensive Applications, 2nd ed. (Kleppmann & Riccomini, 2025), Chapter 12 — Stream Processing, §"Types of windows". The book walks through tumbling, hopping, sliding, and session windows and uses two events at 10:03:39 and 10:08:12 to illustrate how a tumbling window can put nearby events in different buckets while a sliding window keeps them together:
A tumbling window has a fixed length, and every event belongs to exactly one window. […] A sliding window […] contains all the events that occur within some interval of each other.
This exercise runs those exact two timestamps through all four window operators and prints the grouping each one produces.
The surprise is that a window's type, not its size, decides the grouping. These help; each line notes where it shows up.
EVENTS list by its timestamps.run_* function over the same list.SIZE, HOP, and GAP constants.size/slide/gap); Google's Dataflow model paper (Akidau et al., 2015, "The Dataflow Model") is the free primary source for windows as a first-class concept. The one that carries the result: #2. "Window TYPE, not just size" is the whole exercise — the same 5-minute size produces four different answers purely because the four operators draw boundaries differently.
Four operators, same stream, same 5-minute size. Each answers a different question about the stream, so each draws boundaries differently.
| Operator | Boundaries | A window is… | The question it answers |
|---|---|---|---|
| Tumbling | fixed grid, no overlap (…:00, :05, :10) | one bucket per event | "how many events in this clock bucket?" |
| Hopping | fixed grid, advances by a small hop (overlap) | size / hop overlapping buckets per event | "how many events in any 5-min span, sampled every minute?" |
| Sliding | relative to each event, looking back size | one window per event | "how many events happened within 5 min of this one?" |
| Session | no grid — a gap of inactivity closes it | whatever a burst spans | "how many events in this burst of activity?" |
Tumbling and hopping snap to a global clock grid; sliding is relative to each event; session ignores the clock entirely and follows the data. The whole thing is one script, code/window_types.py, running all four over the identical list.
cd study/ddia/ch12/code
python3 window_types.py
Do not read the output yet — make each prediction first. The two events under the microscope are e1 @ 10:03:39 and e2 @ 10:08:12 (4m33s apart). Events e3–e5 add a burst-then-pause tail so the session window has something to group.
Predict. A sliding window anchors a 5-minute window at each event and looks back. The window anchored at e2 (10:08:12) covers the previous 5 minutes. Does e1 (10:03:39) fall inside it — are the two events grouped together?
TOGETHER, count 2. The window ending at 10:08:12 reaches back to 10:03:12, and 10:03:39 is after that — so both events co-occur. Because the window is relative to the event, any two events within 5 minutes of each other land together.
Predict. A tumbling window chops time into fixed non-overlapping 5-minute buckets aligned to …:00, :05, :10. Same two events — same 4m33s gap. Which bucket does each land in? Together or split?
SPLIT — count 1 + 1. The 10:05 grid line falls between the two events, so they straddle a boundary and land in different buckets. The gap between them (4m33s) is smaller than the window (5 min), yet the fixed grid still separates them — because alignment, not distance, decides a tumbling bucket.
Predict. A hopping window is 5 minutes wide but starts a new window every 1 minute, so windows overlap and each event belongs to five of them at once. The two events are less than 5 minutes apart — so some overlapping 5-minute window must contain both, right?
Disjoint — they share no window. For a single 5-min window to hold both, its start would have to fall in (10:03:12, 10:03:39] — but the hop grid only offers starts on the minute (…10:03:00, 10:04:00…), and none land in that 27-second slot. So even overlapping windows, snapped to a grid, separate the pair. The last window holding e1 is [10:03, 10:08) (ends just before 10:08:12); the first holding e2 is [10:04, 10:09) (starts just after 10:03:39).
Predict. A session window has no grid at all: it keeps grouping events until a gap of more than 30 minutes of silence, then starts a new session. The stream is a burst (10:03–10:10), then 35 minutes of silence, then a second burst (10:45–10:47). How many sessions, and where do e1 and e2 land?
Two sessions; e1 and e2 TOGETHER in session 1. No 5-minute boundary appears anywhere — the only thing that closes a window is the 35-minute silence (2100s > 30 min) before e4. The grouping tracks the shape of the activity, so a session can be far longer (here 6m21s and 2m30s) or shorter than 5 minutes.
The same pair, e1 @ 10:03:39 and e2 @ 10:08:12, grouped four ways:
e2 holds both, count 2.[10:00, 10:05) vs. [10:05, 10:10), count 1 + 1."5 minutes" is a length, and a length alone does not group anything. To turn a duration into a window you have to answer three more questions the word "5 minutes" leaves open: where do the boundaries go, do windows overlap, and are boundaries drawn on the clock or on the data? Each operator answers those differently, and the answer — not the 5 minutes — is what decides whether two events share a window.
Tumbling and hopping both snap boundaries to a global grid. A grid line is placed without any reference to the events; it just falls where …:05 falls. Two events 4m33s apart are closer than the 5-minute width, but if a grid line lands between them they are separated anyway. That is exactly what happens: the 10:05 line sits between 10:03:39 and 10:08:12, so tumbling splits them, and for hopping, no minute-aligned 5-minute window happens to start in the 27-second slot (10:03:12, 10:03:39] that would be needed to cover both. The separation is a property of where the grid falls relative to the events, which the size never controls.
Sliding removes the grid: it anchors the window at each event and looks back size. Now "within 5 minutes of each other" is the literal test, so any two events closer than the width co-occur — 10:03:39 is inside (10:03:12, 10:08:12], so e1 and e2 are together, guaranteed, regardless of where a clock grid would have fallen. Session goes further and ignores the clock and the size entirely: it grows a window for as long as events keep arriving within gap of each other, so the boundary is drawn by the data's own silence. Same two events, same 4m33s, four operators — and the grouping is whatever the operator's boundary rule says, never what "5 minutes" says.
size: events closer than 5 minutes may or may not share a window, and which depends only on boundary alignment — the grid position for tumbling/hopping, the anchor for sliding. Push the pair past 5 minutes apart and tumbling, hopping, and sliding all agree ("apart"); only session can still group them, because its ceiling is the 30-minute gap, not the 5-minute size. The whole surprise is that alignment, not duration, is the free variable.
gap closes it. Shrink GAP toward the inter-event spacing and watch session 1 fracture into more, smaller sessions; grow it past the 35-min silence and the two bursts merge into one. Unlike the grid windows, a session can't be emitted until gap of silence has actually elapsed.GRID = t("10:02:30")) and watch e1 and e2 suddenly land in the same tumbling bucket — proof that alignment, not size, carried the split.Sources: DDIA 2e, Ch. 12, §"Types of windows" · Apache Flink Windows documentation · Akidau et al., "The Dataflow Model" (2015).