studyDDIAChapter 12 · Stream Processing

One Stream, One "5-Minute" Width, Four Different Groupings

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.


Concept

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:

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.

Provenance

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.

Prerequisites

The surprise is that a window's type, not its size, decides the grouping. These help; each line notes where it shows up.

  1. Event-time windows — a window groups events by when they happened (their event-time timestamp), not when they were processed. It shows up as every operator here bucketing the fixed EVENTS list by its timestamps.
  2. Tumbling vs. hopping vs. sliding vs. session — four ways to draw window boundaries: non-overlapping grid buckets; overlapping grid buckets; one window per event; or "whatever a burst of activity spans." Each is a different run_* function over the same list.
  3. Window size vs. hop vs. gapsize is the width (5 min for all four); hop is how far a hopping window advances (1 min); gap is the silence that closes a session (30 min). It shows up as the SIZE, HOP, and GAP constants.
Where to learn the prerequisites Window types (#1–#2): DDIA §"Types of windows"; the Apache Flink docs on Windows are the clearest free reference — tumbling, sliding, and session windows with pictures. Size vs. hop vs. gap (#3): the same Flink page names them (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.
Environment these numbers came from Deterministic, so the groupings reproduce exactly. Captured on macOS 26.5.2 (Darwin 25.5.0), arm64, Python 3.15.0a8, standard library only — no Docker. Fixed event list, deterministic — the counts are the same every run.

Mental model

Four operators, same stream, same 5-minute size. Each answers a different question about the stream, so each draws boundaries differently.

OperatorBoundariesA window is…The question it answers
Tumblingfixed grid, no overlap (…:00, :05, :10)one bucket per event"how many events in this clock bucket?"
Hoppingfixed grid, advances by a small hop (overlap)size / hop overlapping buckets per event"how many events in any 5-min span, sampled every minute?"
Slidingrelative to each event, looking back sizeone window per event"how many events happened within 5 min of this one?"
Sessionno grid — a gap of inactivity closes itwhatever 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.

Setup

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 e3e5 add a burst-then-pause tail so the session window has something to group.


Step 1 · sliding: a window per event, looking back

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?

Are e1 and e2 grouped together under a sliding window?
SLIDING -- a 5-min window anchored at each event, looking back window for e1 = (09:58:39, 10:03:39]: e1 (count 1) window for e2 = (10:03:12, 10:08:12]: e1, e2 (count 2) window for e3 = (10:05:00, 10:10:00]: e2, e3 (count 2) window for e4 = (10:40:00, 10:45:00]: e4 (count 1) window for e5 = (10:42:30, 10:47:30]: e4, e5 (count 2) window anchored at e2 (10:08:12) looks back to 10:03:12 e1 @ 10:03:39 falls inside it -> e1 and e2 TOGETHER (count 2).

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.

Step 2 · tumbling: fixed buckets on a clock grid

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?

Which buckets do e1 and e2 land in — together or split?
TUMBLING -- fixed 5-min buckets on a :00 / :05 / :10 grid (no overlap) e1 @ 10:03:39 -> bucket [10:00, 10:05) e2 @ 10:08:12 -> bucket [10:05, 10:10) e3 @ 10:10:00 -> bucket [10:10, 10:15) e4 @ 10:45:00 -> bucket [10:45, 10:50) e5 @ 10:47:30 -> bucket [10:45, 10:50) bucket [10:00, 10:05): e1 (count 1) bucket [10:05, 10:10): e2 (count 1) bucket [10:10, 10:15): e3 (count 1) bucket [10:45, 10:50): e4, e5 (count 2) e1 in [10:00, 10:05) e2 in [10:05, 10:10) -> e1 and e2 are SPLIT into DIFFERENT buckets (count 1 + count 1).

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.

Step 3 · hopping: overlapping buckets on the grid

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?

Does some overlapping 5-minute window contain both events?
HOPPING -- 5-min windows, new one every 1 min (they OVERLAP) e1 @ 10:03:39 -> 5 windows: [09:59, 10:04), [10:00, 10:05), [10:01, 10:06), [10:02, 10:07), [10:03, 10:08) e2 @ 10:08:12 -> 5 windows: [10:04, 10:09), [10:05, 10:10), [10:06, 10:11), [10:07, 10:12), [10:08, 10:13) shared windows: NONE -> e1 and e2 land in overlapping-but-DIFFERENT sets of windows; no single 5-min window on the hop grid straddles the pair.

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).

Step 4 · session: grouped by a gap, not a clock

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?

How many sessions, and where do e1 and e2 land?
SESSION -- grouped by a 30-min inactivity gap (clock ignored) e1 @ 10:03:39 e2 @ 10:08:12 (gap 273s = <= 30m: same session) e3 @ 10:10:00 (gap 108s = <= 30m: same session) e4 @ 10:45:00 (gap 2100s = > 30m: NEW session) e5 @ 10:47:30 (gap 150s = <= 30m: same session) session 1 [10:03:39-10:10:00]: e1, e2, e3 (count 3) session 2 [10:45:00-10:47:30]: e4, e5 (count 2) -> e1 and e2 are TOGETHER in session 1 -- grouped by activity, not by any 5-minute clock boundary.

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.


What you should see

The same pair, e1 @ 10:03:39 and e2 @ 10:08:12, grouped four ways:

Why

"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.

The boundary — where the effect vanishes Events farther apart than the window size are never grouped by tumbling, hopping, or sliding — no 5-minute window can hold two events more than 5 minutes apart, so for those three the size is a hard ceiling on togetherness. The divergence in this exercise lives entirely in the band within 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.

Go deeper

Sources: DDIA 2e, Ch. 12, §"Types of windows" · Apache Flink Windows documentation · Akidau et al., "The Dataflow Model" (2015).