Windowing in Stream Processing: Tumbling, Hopping, Sliding, and Session Windows

Windowing in Stream Processing: Tumbling, Hopping, Sliding, and Session Windows

Every streaming application eventually faces the same question: how do you compute meaningful aggregates over data that never stops arriving? You cannot run a simple GROUP BY over an infinite stream the way you would over a finite table. You need boundaries, and in stream processing, those boundaries are called windows.

Windowing is the mechanism that slices a continuous data stream into finite chunks so you can compute aggregations like counts, averages, and sums. Choosing the right window type directly affects the accuracy of your results, the latency of your pipeline, and the resources your system consumes. Get it wrong, and you either miss important patterns or drown in redundant computation.

This guide covers the four fundamental window types in stream processing: tumbling, hopping, sliding, and session windows. For each type, you will see how it works conceptually, when to use it, and how to implement it with SQL in RisingWave, an open-source streaming database that lets you write stream processing logic in standard SQL.

What Is Windowing in Stream Processing?

A window is a finite view over a portion of a data stream, defined by time or by some other boundary. Windowing functions assign each incoming event to one or more windows, enabling aggregation operations that produce meaningful results from unbounded data.

In RisingWave, time windows are used in the FROM clause. They take a table, source, or materialized view along with a time column, and augment each row with two additional columns: window_start and window_end. These columns define the boundaries of the window to which each row belongs.

There are four primary window types, each suited to different analytical patterns:

Window TypeOverlapping?Fixed Size?Best For
TumblingNoYesPeriodic reports, batch-style aggregation
HoppingYesYesSmoothed metrics, trend detection
SlidingYesYesContinuous monitoring, moving averages
SessionNoNoUser behavior analysis, activity tracking

Let's walk through each one.

Tumbling Windows: Fixed, Non-Overlapping Intervals

A tumbling window divides the stream into contiguous, non-overlapping time intervals of equal length. Each event belongs to exactly one window. When one window ends, the next one begins immediately.

Time ──────────────────────────────────────────────────►

│  Window 1   │  Window 2   │  Window 3   │
│ [00:00-05:00) │ [05:00-10:00) │ [10:00-15:00) │
│  ● ● ● ● ●  │  ● ● ● ● ●  │  ● ●        │

● = event

Tumbling windows are the simplest and most commonly used window type. Because windows do not overlap, each event is processed exactly once, which makes tumbling windows efficient in terms of both computation and storage.

When to Use Tumbling Windows

  • Periodic reporting: Generate a count of page views every 5 minutes, or calculate revenue per hour.
  • Batch-style aggregation on streams: When you need results that look like traditional batch reports but update continuously.
  • Resource-constrained environments: Since each event is in exactly one window, memory usage is predictable.

Tumbling Window SQL in RisingWave

RisingWave provides the TUMBLE() function for tumbling windows. The syntax is:

SELECT ...
FROM TUMBLE(table_or_source, time_col, window_size [, offset])
GROUP BY window_start, window_end;

Here is a concrete example. Suppose you have a page_views table that tracks website visits:

CREATE TABLE page_views (
    user_id INT,
    page_url VARCHAR,
    view_time TIMESTAMP,
    duration_seconds INT
);

To count page views and unique users in 5-minute tumbling windows:

SELECT
    window_start,
    window_end,
    COUNT(*) AS view_count,
    COUNT(DISTINCT user_id) AS unique_users,
    SUM(duration_seconds) AS total_duration
FROM TUMBLE(page_views, view_time, INTERVAL '5 MINUTES')
GROUP BY window_start, window_end
ORDER BY window_start;
    window_start     |     window_end      | view_count | unique_users | total_duration
---------------------+---------------------+------------+--------------+----------------
 2026-04-01 10:00:00 | 2026-04-01 10:05:00 |          5 |            3 |            245
 2026-04-01 10:05:00 | 2026-04-01 10:10:00 |          5 |            5 |            275
 2026-04-01 10:10:00 | 2026-04-01 10:15:00 |          2 |            2 |            130

Each 5-minute interval produces exactly one row per group. The windows are contiguous: 10:00-10:05, 10:05-10:10, 10:10-10:15. No gaps, no overlaps.

You can turn this query into a materialized view that updates incrementally as new data arrives:

CREATE MATERIALIZED VIEW mv_page_views_5min AS
SELECT
    window_start,
    window_end,
    COUNT(*) AS view_count,
    COUNT(DISTINCT user_id) AS unique_users,
    ROUND(AVG(duration_seconds)::numeric, 1) AS avg_duration
FROM TUMBLE(page_views, view_time, INTERVAL '5 MINUTES')
GROUP BY window_start, window_end;

Query the materialized view like any regular table:

SELECT * FROM mv_page_views_5min ORDER BY window_start;
    window_start     |     window_end      | view_count | unique_users | avg_duration
---------------------+---------------------+------------+--------------+--------------
 2026-04-01 10:00:00 | 2026-04-01 10:05:00 |          5 |            3 |           49
 2026-04-01 10:05:00 | 2026-04-01 10:10:00 |          5 |            5 |           55
 2026-04-01 10:10:00 | 2026-04-01 10:15:00 |          2 |            2 |           65

The materialized view stays up to date as new rows are inserted into page_views, without you needing to re-run the query.

Hopping Windows: Overlapping Fixed-Size Intervals

A hopping window (sometimes called a "sliding window" in other frameworks) has a fixed window size but advances by a smaller hop interval. This means windows overlap, and a single event can appear in multiple windows.

Time ──────────────────────────────────────────────────►

│──── Window 1 (10 min) ────│
     │──── Window 2 (10 min) ────│
          │──── Window 3 (10 min) ────│
               │──── Window 4 (10 min) ────│

Hop size: 5 minutes
Window size: 10 minutes

The key parameters are:

  • Window size: the total duration of each window
  • Hop size: how far the window advances each time

When hop_size == window_size, a hopping window degenerates into a tumbling window. When hop_size < window_size, windows overlap.

When to Use Hopping Windows

  • Smoothed metrics: A 10-minute average updated every minute gives smoother trend lines than a 1-minute tumbling window.
  • Anomaly detection: Overlapping windows help you catch patterns that straddle window boundaries, which tumbling windows would miss.
  • SLA monitoring: Track the 99th percentile response time over the last 15 minutes, updated every 5 minutes.

Hopping Window SQL in RisingWave

RisingWave provides the HOP() function:

SELECT ...
FROM HOP(table_or_source, time_col, hop_size, window_size [, offset])
GROUP BY window_start, window_end;

Using the same page_views table, compute a 10-minute rolling average with a 5-minute hop:

SELECT
    window_start,
    window_end,
    COUNT(*) AS view_count,
    COUNT(DISTINCT user_id) AS unique_users,
    ROUND(AVG(duration_seconds)::numeric, 1) AS avg_duration
FROM HOP(page_views, view_time, INTERVAL '5 MINUTES', INTERVAL '10 MINUTES')
GROUP BY window_start, window_end
ORDER BY window_start;
    window_start     |     window_end      | view_count | unique_users | avg_duration
---------------------+---------------------+------------+--------------+--------------
 2026-04-01 09:55:00 | 2026-04-01 10:05:00 |          5 |            3 |           49
 2026-04-01 10:00:00 | 2026-04-01 10:10:00 |         10 |            5 |           52
 2026-04-01 10:05:00 | 2026-04-01 10:15:00 |          7 |            5 |         57.9
 2026-04-01 10:10:00 | 2026-04-01 10:20:00 |          2 |            2 |           65

Notice how the 10:00-10:10 window contains 10 events, combining data from what tumbling windows would have split across two separate 5-minute buckets. This overlap is exactly what makes hopping windows useful for detecting trends that span boundary points.

Tumbling vs. Hopping: A Practical Comparison

Consider IoT sensor monitoring. You have temperature readings arriving every 30 seconds from multiple sensors:

CREATE TABLE sensor_readings (
    sensor_id INT,
    temperature DOUBLE PRECISION,
    reading_time TIMESTAMP
);

A 2-minute tumbling window gives you a clean snapshot per interval:

SELECT
    window_start,
    window_end,
    sensor_id,
    ROUND(AVG(temperature)::numeric, 1) AS avg_temp,
    ROUND(MAX(temperature)::numeric, 1) AS max_temp,
    ROUND(MIN(temperature)::numeric, 1) AS min_temp,
    COUNT(*) AS reading_count
FROM TUMBLE(sensor_readings, reading_time, INTERVAL '2 MINUTES')
GROUP BY window_start, window_end, sensor_id
ORDER BY window_start, sensor_id;
    window_start     |     window_end      | sensor_id | avg_temp | max_temp | min_temp | reading_count
---------------------+---------------------+-----------+----------+----------+----------+---------------
 2026-04-01 10:00:00 | 2026-04-01 10:02:00 |         1 |     22.7 |     22.8 |     22.5 |             2
 2026-04-01 10:00:00 | 2026-04-01 10:02:00 |         2 |     23.3 |     23.5 |     23.1 |             2
 2026-04-01 10:02:00 | 2026-04-01 10:04:00 |         1 |     25.7 |     26.1 |     25.2 |             2
 2026-04-01 10:02:00 | 2026-04-01 10:04:00 |         2 |     24.4 |     24.8 |     24.0 |             2
 2026-04-01 10:04:00 | 2026-04-01 10:06:00 |         1 |     24.5 |     24.5 |     24.5 |             1
 2026-04-01 10:04:00 | 2026-04-01 10:06:00 |         2 |     23.9 |     23.9 |     23.9 |             1

This is useful for periodic reporting. But if you need to detect a temperature spike that happens right at a window boundary, a hopping window with a smaller hop ensures you never miss it.

Sliding Windows: Event-Triggered Continuous Updates

A sliding window is conceptually similar to a hopping window but with one key difference: instead of advancing at a fixed hop interval, a sliding window re-evaluates whenever the window contents change (when an event enters or exits the window). In practice, a sliding window behaves like a hopping window where the hop size is infinitely small.

Time ──────────────────────────────────────────────────►

Event arrives at t=3:
  │◄──── 5 min window ────►│
                            t=3

Event arrives at t=4:
   │◄──── 5 min window ────►│
                             t=4

Window slides with each event

Sliding windows produce the most up-to-date view of your data, but they are also the most expensive because every incoming event can trigger a recalculation.

Implementing Sliding Windows in RisingWave

RisingWave does not have a dedicated SLIDE() function, but you can achieve sliding window semantics in two ways:

Option 1: Use HOP with a small hop size. A 5-minute window with a 1-minute hop provides near-continuous updates:

SELECT
    window_start,
    window_end,
    COUNT(*) AS view_count,
    ROUND(AVG(duration_seconds)::numeric, 1) AS avg_duration
FROM HOP(page_views, view_time, INTERVAL '1 MINUTE', INTERVAL '5 MINUTES')
GROUP BY window_start, window_end
ORDER BY window_start;

The smaller the hop, the closer you get to true sliding window behavior. The tradeoff is more output rows and higher resource usage.

Option 2: Use SQL window functions with ROWS or RANGE frames. For per-row sliding calculations, standard SQL window functions work well:

SELECT
    user_id,
    view_time,
    duration_seconds,
    AVG(duration_seconds) OVER (
        ORDER BY view_time
        RANGE BETWEEN INTERVAL '5 MINUTES' PRECEDING AND CURRENT ROW
    ) AS rolling_avg_duration
FROM page_views
ORDER BY view_time;

This computes a rolling 5-minute average for each row individually.

When to Use Sliding Windows

  • Real-time dashboards: Display a "last 5 minutes" metric that updates with every event.
  • Moving averages: Financial price calculations, rolling latency percentiles.
  • Alerting: Trigger an alert when the error rate over the last 10 minutes exceeds a threshold.

Session Windows: Activity-Based Grouping

Session windows are fundamentally different from the time-based windows above. Instead of using a fixed size, session windows group events based on activity. A session starts when an event arrives and extends as long as subsequent events arrive within a specified gap period. When no events arrive for longer than the gap, the session closes.

Time ──────────────────────────────────────────────────►

User 1:
│── Session 1 ──│              │ Session 2 │
● ● ● ●         (gap > 10 min)  ● ●

User 2:
│────────── Session 1 ─────────│
● ● ● ● ●

● = user action, gap threshold = 10 minutes

Session windows are variable-length and non-overlapping. They are data-driven rather than clock-driven, which makes them the natural choice for analyzing user behavior.

When to Use Session Windows

  • User session analysis: Group a user's clickstream into browsing sessions to measure engagement.
  • Fraud detection: Identify clusters of suspicious activity separated by periods of inactivity.
  • IoT device monitoring: Group bursts of sensor activity into operational sessions.

Session Window SQL in RisingWave

RisingWave supports session windows using the SESSION WITH GAP frame in SQL window functions:

SELECT expression OVER (
    PARTITION BY column
    ORDER BY time_col
    SESSION WITH GAP INTERVAL 'gap_duration'
) FROM table_name;

Here is a practical example. You have a user_clicks table tracking e-commerce activity:

CREATE TABLE user_clicks (
    user_id INT,
    action VARCHAR,
    click_time TIMESTAMP
);

Sample data includes two users with different activity patterns. User 1 has two separate sessions (with a 15-minute gap between them), and User 2 has one continuous session:

-- Group clicks into sessions with a 10-minute inactivity gap
SELECT DISTINCT
    user_id,
    first_value(click_time) OVER w AS session_start,
    last_value(click_time) OVER w AS session_end,
    count(*) OVER w AS actions_in_session
FROM user_clicks
WINDOW w AS (
    PARTITION BY user_id ORDER BY click_time
    SESSION WITH GAP INTERVAL '10 MINUTES'
)
ORDER BY user_id, session_start;
 user_id |    session_start    |     session_end     | actions_in_session
---------+---------------------+---------------------+--------------------
       1 | 2026-04-01 10:00:00 | 2026-04-01 10:07:00 |                  4
       1 | 2026-04-01 10:22:00 | 2026-04-01 10:24:00 |                  2
       2 | 2026-04-01 10:01:00 | 2026-04-01 10:11:00 |                  5

User 1 was split into two sessions because there was a 15-minute gap between their 10:07 and 10:22 events. User 2 maintained a single session because all their events were within 10 minutes of each other.

Note: In RisingWave, the SESSION frame currently works in batch mode and emit-on-window-close streaming mode. For continuous streaming session analysis, define a watermark on your source and use EMIT ON WINDOW CLOSE.

Choosing the Right Window Type

The right window depends on your use case. Here is a decision framework:

                    ┌─────────────────────────┐
                    │ Do you need fixed-size   │
                    │ time intervals?          │
                    └────────┬────────┬────────┘
                        Yes  │        │  No
                    ┌────────▼───┐    │
                    │ Do windows │    ▼
                    │ need to    │  Session
                    │ overlap?   │  Windows
                    └──┬─────┬──┘
                  No   │     │  Yes
                  ▼    │     ▼
               Tumble  │    HOP / Sliding
                       │
ScenarioRecommended WindowWhy
Hourly sales reportTumbling (1 hour)Clean, non-overlapping intervals for reporting
5-minute moving average of latencyHopping (5 min window, 1 min hop)Overlapping windows smooth out spikes
Real-time error rate dashboardSliding (or small-hop HOP)Continuous updates as events arrive
User browsing session analysisSession (10-15 min gap)Activity-based grouping matches user behavior
IoT sensor batch processingTumbling (matching batch interval)Aligns with physical data collection patterns
Fraud detection over recent activityHopping (30 min window, 5 min hop)Catch patterns that cross boundaries

EMIT ON WINDOW CLOSE: Controlling When Results Appear

By default, RisingWave materialized views emit updated results continuously as new data arrives. For window-based queries, this means you see partial results for windows that have not yet closed. In many cases, you only want the final result after a window has fully closed.

The EMIT ON WINDOW CLOSE clause tells RisingWave to wait until a window is complete before producing output. This requires a watermark on your source to signal event-time progress:

CREATE SOURCE page_view_source (
    user_id INT,
    page_url VARCHAR,
    view_time TIMESTAMP,
    WATERMARK FOR view_time AS view_time - INTERVAL '5 SECONDS'
) WITH (
    connector = 'kafka',
    topic = 'page_views',
    properties.bootstrap.server = 'localhost:9092'
);

CREATE MATERIALIZED VIEW mv_final_counts AS
SELECT
    window_start,
    window_end,
    COUNT(*) AS view_count
FROM TUMBLE(page_view_source, view_time, INTERVAL '1 MINUTE')
GROUP BY window_start, window_end
EMIT ON WINDOW CLOSE;

Use EMIT ON WINDOW CLOSE when:

  • Your downstream sink (Kafka, S3, Iceberg) expects append-only, finalized results
  • You want to avoid partial/intermediate updates
  • You are running expensive aggregations like percentiles that do not benefit from incremental computation

What is the difference between tumbling and hopping windows?

Tumbling windows are a special case of hopping windows where the hop size equals the window size. In a tumbling window, every event belongs to exactly one window, and windows never overlap. In a hopping window, the hop size is smaller than the window size, so windows overlap and a single event can be counted in multiple windows. Use tumbling windows when you need clean, non-overlapping intervals for reporting. Use hopping windows when you need smoothed metrics or want to catch patterns that happen near window boundaries.

When should I use session windows instead of tumbling windows?

Use session windows when your analysis is driven by user activity patterns rather than fixed time intervals. Session windows group events that occur close together in time and separate them when there is a gap of inactivity. This makes them ideal for analyzing user browsing sessions, tracking engagement duration, or detecting bursts of IoT sensor activity. Tumbling windows, by contrast, impose arbitrary boundaries that can split a single logical session across multiple windows.

How does RisingWave handle late-arriving data in windowed queries?

RisingWave uses watermarks to handle late-arriving data. A watermark is a timestamp that declares "all events with timestamps before this point have arrived." You define watermarks when creating sources, typically with a tolerance for lateness (for example, WATERMARK FOR event_time AS event_time - INTERVAL '5 SECONDS'). Events that arrive after the watermark has passed beyond their window may be dropped, depending on your configuration. The emit-on-window-close documentation covers this in detail.

Can I create materialized views with window functions in RisingWave?

Yes. RisingWave supports creating materialized views over windowed queries using both TUMBLE() and HOP(). These materialized views are incrementally maintained, meaning RisingWave updates results as new data streams in without re-scanning the entire dataset. You can query them like regular tables with sub-millisecond latency. For session windows, use the EMIT ON WINDOW CLOSE mode with a watermark-enabled source.

Conclusion

Windowing is the foundation of meaningful stream processing. Without windows, you cannot aggregate unbounded data into actionable results. Here are the key takeaways:

  • Tumbling windows give you clean, non-overlapping intervals. Use them for periodic reports and batch-style aggregation. In RisingWave, use the TUMBLE() function.
  • Hopping windows provide overlapping intervals that smooth out metrics and catch boundary-spanning patterns. In RisingWave, use the HOP() function.
  • Sliding windows offer continuous, event-triggered updates. Approximate them in RisingWave using HOP() with a small hop size or SQL window functions with RANGE frames.
  • Session windows group events by activity rather than time. In RisingWave, use SESSION WITH GAP in SQL window functions.
  • EMIT ON WINDOW CLOSE controls when results appear, producing only finalized output after windows close.

All the SQL examples in this post were tested against RisingWave 2.8.0. You can run them yourself in minutes.


Ready to try windowed stream processing yourself? Get started with RisingWave in 5 minutes. Quickstart →

Join our Slack community to ask questions and connect with other stream processing developers.

Best-in-Class Event Streaming
for Agents, Apps, and Analytics
GitHubXLinkedInSlackYouTube
Sign up for our to stay updated.