In Azure Data Factory, how do you make each hourly tumbling window wait for the previous window?
answer
- windows can wait on other windows
- a trigger may depend on itself
- two properties: how far back, how much
- concurrency limits alone do not order anything
basics
~20 sAdd a self-dependency to the tumbling window trigger: a dependsOn entry of type SelfDependencyTumblingWindowTriggerReference with size equal to the window and a negative offset of one window, so each window waits for its predecessor to succeed.
solid answer
~40 sTumbling window triggers support a `dependsOn` array, and a **self-dependency** is the standard way to serialise slices. For an hourly trigger you add an entry of type `SelfDependencyTumblingWindowTriggerReference` with `size` of `01:00:00` and `offset` of `-01:00:00`: window N will not start until window N-1 has succeeded. Keep `maxConcurrency` at 1 alongside it so a cleared backlog does not fan out. A window whose dependency has not succeeded stays waiting rather than running or failing, so one broken hour blocks everything behind it — which is exactly what you want when the load is stateful (`depends_on_past` in spirit), and exactly what you do not want for independent slices. The same `dependsOn` mechanism, using `TumblingWindowTriggerDependencyReference`, makes one trigger's windows wait on another trigger's windows, so a daily rollup can wait for 24 hourly loads.
code
json · 27 lines{
"name": "HourlyBalances",
"properties": {
"type": "TumblingWindowTrigger",
"typeProperties": {
"frequency": "Hour",
"interval": 1,
"startTime": "2026-06-01T00:00:00Z",
"maxConcurrency": 1,
"retryPolicy": { "count": 2, "intervalInSeconds": 600 },
"dependsOn": [
{
"type": "SelfDependencyTumblingWindowTriggerReference",
"size": "01:00:00",
"offset": "-01:00:00"
}
]
},
"pipeline": {
"pipelineReference": { "referenceName": "LoadBalances", "type": "PipelineReference" },
"parameters": {
"windowStart": "@trigger().outputs.windowStartTime",
"windowEnd": "@trigger().outputs.windowEndTime"
}
}
}
}go deeper
Know only that tumbling windows can be made to run in order, and that ordering is configured on the trigger rather than inside the pipeline.
Explain the two dependency properties — how far back the dependency points and how much of the upstream timeline it covers — and why a concurrency cap is not an ordering guarantee.
Show the operational consequence: a blocked chain queues silently, drains in order once the blocker is rerun, and needs freshness alerting rather than failure alerting. Say when you would deliberately not add the dependency.
Own the policy for stateful versus partition-independent loads, how backfill concurrency is chosen against source capacity, and who is allowed to clear a blocked chain in production.
## Why window dependencies exist A tumbling window trigger creates a run per time slice, and by default those runs are independent: with `maxConcurrency` above 1, several windows can be in flight at once, and a failed window does not stop the next one. That is fine for a load that overwrites its own partition. It is wrong for anything cumulative — a running balance, a snapshot chain, a target table whose merge assumes the prior window is present. ADF's answer is a dependency declared on the trigger, not on the pipeline's activities. ## Self-dependency A self-dependency makes each window wait for an earlier window of the *same* trigger. It is expressed as an entry in `dependsOn`: ```json "dependsOn": [ { "type": "SelfDependencyTumblingWindowTriggerReference", "size": "01:00:00", "offset": "-01:00:00" } ] ``` Two properties do the work. `offset` shifts the dependency backwards in time — a negative offset of exactly one window length points at the immediately preceding window. `size` says how much of that shifted timeline must be satisfied; for a one-to-one predecessor relationship it equals the window length. The direction trips people up: a *positive* offset points forward and is not what a "wait for the previous run" requirement wants. Pair a self-dependency with `maxConcurrency: 1`. Concurrency alone only limits how many windows are in flight; it does not make window N wait for N-1 to *succeed*, and it does not stop the chain when a window fails. The dependency is what supplies ordering and blocking. ## Cross-trigger dependency The same array can name another tumbling window trigger: ```json "dependsOn": [ { "type": "TumblingWindowTriggerDependencyReference", "referenceTrigger": { "referenceName": "HourlyLoad", "type": "TriggerReference" }, "size": "1.00:00:00", "offset": "00:00:00" } ] ``` Here a daily window waits for a full day's worth of the hourly trigger's windows, because `size` spans a day of the upstream timeline. This is how a fan-in is expressed in ADF: not with an activity-level sensor, but by declaring at the trigger level which upstream slices a downstream slice consumes. Because both sides are window-aware, a backfill of the hourly trigger naturally unblocks the daily windows behind it. ## What a waiting window looks like In Monitor, trigger runs for a tumbling window trigger are listed per window with their own status. A window whose dependency is not yet satisfied is shown as waiting on its dependency rather than as failed, and it consumes no pipeline run. When the upstream window finally succeeds — often after a manual rerun — the dependent windows become runnable and drain in order, bounded by `maxConcurrency`. This is why an overnight outage produces a queue, not a hole: the windows exist, they are waiting, and clearing the blocker clears the backlog. It is also why a dependency chain needs alerting. Silence from a dependency-blocked chain looks identical to "nothing to do", so a freshness check on the target table matters more than a failure alert on the pipeline. ## Rerunning a window Because each window is addressable, the repair path is to fix the cause and rerun the specific window from the trigger runs view. The rerun receives the same `windowStartTime` and `windowEndTime`, so an idempotent pipeline — one that deletes-and-reloads or merges its own slice rather than blindly appending — produces the same result as the original run would have. If the pipeline appends, a rerun duplicates, and no amount of trigger configuration will fix that; idempotency is a property of the pipeline body, not of the trigger. ## Backfill interaction A backfill (a `startTime` in the past) plus a self-dependency means the historical windows replay strictly in order, one at a time. That is safe but slow: three months of hourly windows is a long serial queue. When the load is actually partition-independent, drop the self-dependency, raise `maxConcurrency` deliberately, and let the source's capacity — not the trigger — be the reason you throttle. Cost and source pressure during a backfill are governed by `maxConcurrency`, and setting it high on a shared source is a classic self-inflicted incident. ## Pitfalls to name in an interview - Expecting `maxConcurrency: 1` alone to enforce ordering and blocking. - Getting the sign of `offset` wrong and creating a dependency on a future window. - Adding a self-dependency to a load that does not need it, then wondering why a two-hour outage takes a day to drain. - Assuming a dependent window is skipped when its upstream fails; it waits instead, indefinitely, until someone acts. - Trying to express these dependencies between *schedule* triggers, which have no window state and therefore no dependency support.
- What happens to a window whose dependency never succeeds?It waits rather than failing or being skipped, and every window behind it queues up too. Nothing runs until someone fixes the upstream cause and reruns that window, after which the backlog drains in order under maxConcurrency. The failure mode is silence, so alert on target freshness, not only on pipeline failure.
- How would you express a daily rollup that needs the whole day of hourly windows?On the daily trigger add a dependsOn entry of type TumblingWindowTriggerDependencyReference pointing at the hourly trigger, with size spanning one day. The daily window then waits for the day's worth of upstream windows to succeed, and a backfill of the hourly trigger automatically releases it.
- Does a self-dependency remove the need for the pipeline to be idempotent?No. Ordering says only that the previous window finished, not that a rerun of this window is safe. If the pipeline appends rows, rerunning a window duplicates them regardless of dependencies. Idempotency comes from the pipeline body — overwrite the window's partition or merge on a key using the window boundaries.
A self-dependency is a relay baton: the next runner cannot leave the blocks until the previous one has actually handed it over, no matter how many lanes the track has.
saying these in an interview costs you the question
- Thinks maxConcurrency of 1 also blocks after a failure
- Uses a positive offset to point at the previous window
- Believes a dependent window is skipped when upstream fails
- Adds self-dependency to slices that are actually independent
- Tries to declare dependencies between schedule triggers