What is windowing in stream processing?
Updated Feb 20, 2026
Short answer
Windowing slices an unbounded stream into finite chunks so that aggregations can produce results. A stream never ends, so 'count the events' has no answer without a boundary — a window supplies one. The main types are tumbling, sliding, and session windows, and the hard part is deciding when a window is complete given that events arrive late and out of order.
Deep explanation
events: a b c d e f g
Tumbling (60s) [ a b c ][ d e ][ f g ] fixed, non-overlappingSliding (60s/30s)[ a b c ] [ b c d ] fixed, overlapping [ c d e ]Session (30s gap)[ a b c ] [ d e ][ f g ] boundary = inactivity- Tumbling — fixed size, no overlap. Every event belongs to exactly one window. Use for periodic reporting: revenue per hour, errors per minute.
- Sliding — fixed size, advancing by a smaller step, so windows overlap and an event appears in several. Use for rolling metrics: "average latency over the last 5 minutes, updated every 30 seconds".
- Session — no fixed size; the window closes after a gap of inactivity. Use for user-behaviour analysis, where the natural unit is a visit rather than a clock interval.
The genuinely hard part is time and lateness. Event time is when the event actually happened; processing time is when your system saw it. A phone that was offline for an hour delivers events with an event time long past. Windowing on processing time is simple but wrong — it puts that event in the current window rather than the one it belongs to.
Systems resolve this with watermarks: an assertion that no event older than time T is expected any more. A window closes when the watermark passes its end.
stream \ .assign_timestamps_and_watermarks( WatermarkStrategy.for_bounded_out_of_orderness(Duration.of_seconds(30))) .key_by(lambda e: e.user_id) \ .window(TumblingEventTimeWindows.of(Time.minutes(1))) \ .sum('amount')The watermark delay is a direct trade-off: longer means more late events are included but every result is delayed; shorter means faster results and more data dropped. Anything arriving after the watermark is late data, and you must choose explicitly — discard it, send it to a side output, or re-open the window and emit a correction.
Real-world example
Fraud detection needs "more than 5 transactions from one card in 60 seconds":
transactions \ .key_by(lambda t: t.card_id) \ .window(SlidingEventTimeWindows.of(Time.seconds(60), Time.seconds(10))) \ .apply(lambda key, window, events: Alert(key) if len(events) > 5 else None)Sliding rather than tumbling is essential here. With tumbling 60-second windows, three transactions at 0:59 and three at 1:01 land in different windows and neither trips the threshold — despite six transactions in two seconds. Sliding windows advancing every 10 seconds catch the burst wherever it falls.
Common mistakes
- - Windowing on processing time when the question is about event time, so delayed events are attributed to the wrong period and results silently drift.
- - Using tumbling windows for threshold detection, where an event burst straddling a boundary is split and missed.
- - Setting the watermark delay too aggressively and dropping legitimate late data with no visibility into how much was lost.
- - Ignoring late data entirely rather than routing it to a side output — you cannot fix what you never recorded.
- - Using session windows without a state time-to-live, so keys that never see another event retain state forever and leak memory.
- - Forgetting that sliding windows multiply state: a 60s window advancing every 1s keeps 60 windows open per key at once.
Follow-up questions
- What is a watermark and how do you choose its delay?
- What are the options for handling late data?
- How do session windows differ from the other types?
- Why do sliding windows cost more state than tumbling ones?