Apache Airflow Schedule Interval
Cron, Data Interval, Logical Date
The Apache Airflow schedule interval is the rule in a Dag's schedule argument that decides when the scheduler creates a run: a cron expression, a timedelta, a preset or a timetable. Each run covers a data interval, and under a data interval timetable it starts after that interval ends. Its logical date, the old execution date, is the start of the range, while the run itself happens later.
What is the Apache Airflow schedule interval?
In Apache Airflow, the schedule interval is the rule that decides when the scheduler creates a Dag run. You set it with the schedule argument of a Dag, which accepts a cron expression, a timedelta, a cron preset such as @daily, or a timetable object. Airflow 3.0 removed the older schedule_interval and timetable arguments, so schedule is now the only one.
Every run also carries a data interval, the time range of data it is meant to process. The scheduler creates a run only after its data interval has ended when you use a data interval timetable, and that delay is the source of most confusion about the schedule. A run named for 00:00 on day 1 starts at 00:00 on day 2.
Statements here come from the Airflow timetables documentation, which is versioned 3.3.2 at the time of writing.
Know Airflow schedule interval the way the interviewer who asks it knows it.
What is the data interval in Airflow?
The data interval is the range of time a Dag run is responsible for, exposed to your tasks as data_interval_start and data_interval_end. For a daily cron schedule, 0 0 * * *, each interval is 24 hours long. The run for the interval that begins at 00:00 on day 1 is created at 00:00 on day 2, when the last of its data has arrived.
That's the behavior of a data interval timetable, and it's why a Dag scheduled for midnight seems to run a day late. A trigger timetable works differently. It fires at the cron time and gives the run an interval whose start and end are both that trigger time, so the interval is 0 hours long unless you ask for one with the interval argument.


The practical rule is to write tasks against data_interval_start and data_interval_end instead of the wall clock. A task that filters rows between the 2 values returns the same rows when you rerun it next week, and a task that reads now() does not.
Logical date vs execution date in Airflow
The logical date is the new name for what Airflow 2 called the execution date, and the name was always misleading, because it isn't when the run executes. The Dag run documentation says the logical date "denotes the start of the data interval, not when the Dag is actually executed". Airflow 3.0 removed the execution_date context variable, along with next_execution_date, prev_execution_date, next_ds and prev_ds, and tells you to read dag_run.logical_date instead.
The Airflow 3 upgrade guide lists those removals and warns that leaving them in a Dag will cause Dag errors. Manual runs changed too: Airflow no longer accepts a logical date in the future, and logical_date=None triggers a run with the current timestamp.
For a manually triggered run, don't assume the data interval follows from the logical date you supplied. If a Dag needs the date the user chose, read logical_date explicitly. If it needs the range of data to process, read the data interval.
The 4 timetables behind the schedule argument
- Takes
- Data interval
- Run is created
- Default for cron
- TakesCron expression
- Data intervalStart equals end
- Run is createdAt the trigger
- Default for cronYes, Airflow 3
- TakesCron expression
- Data intervalBetween cron ticks
- Run is createdAt interval end
- Default for cronNo
- TakesA timedelta
- Data intervalStart equals end
- Run is createdOnce a delta passes
- Default for cronNo
- TakesA timedelta
- Data intervalOne delta wide
- Run is createdAt interval end
- Default for cronNo
Trigger vs data interval timetables, and which one a cron string gets
A string such as 0 0 * * * is shorthand for a timetable, and which one it becomes is set by the create_cron_data_intervals option in the [scheduler] section. It was added in Airflow 2.9.0. Its default is False, which means a bare cron string becomes a CronTriggerTimetable. Set to True, it becomes a CronDataIntervalTimetable. The Airflow 3 upgrade guide notes the default change, and says it only affects Dags that pass a bare cron string, so a Dag that names its timetable is unaffected.
The difference shows in the first run. The timetables documentation enables a daily Dag at 15:00 on January 31. The trigger timetable creates its first run at midnight on February 1, while the data interval timetable creates a run immediately, with a run id timestamp of midnight on January 31. If the Dag is paused on January 31 and enabled again on February 2, the trigger timetable skips the runs for February 1 and 2, and the data interval timetable skips only February 1 and triggers February 2 at once.
If you want a trigger timetable that still carries a real data interval, pass interval. The documented example, 0 18 * * 5 with timedelta(days=4, hours=9), runs on Fridays at 18:00 and covers Monday 09:00 to that Friday, which is 4 x 24 + 9 = 105 hours. For several cron expressions on 1 Dag, MultipleCronTriggerTimetable runs whenever any of them matches.
Writing the schedule as a timetable object
import pendulum
from datetime import timedelta
from airflow.sdk import DAG
from airflow.timetables.interval import CronDataIntervalTimetable
from airflow.timetables.trigger import CronTriggerTimetable
# A trigger timetable: runs at 18:00 on Fridays.
# interval= gives each run a data interval that ends at the trigger time.
with DAG(
dag_id="weekly_report",
start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
schedule=CronTriggerTimetable(
"0 18 * * 5", timezone="UTC",
interval=timedelta(days=4, hours=9),
),
catchup=False,
):
...
# A data interval timetable: each run covers the span between 2 cron ticks.
with DAG(
dag_id="daily_load",
start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
schedule=CronDataIntervalTimetable("0 0 * * *", timezone="UTC"),
catchup=False,
):
...Airflow cron expression and presets
An Airflow cron expression has 5 fields: minute, hour, day of month, month and day of week. Airflow parses it with croniter, so extended forms work, for instance 0 0 * * MON#1 for midnight on the first Monday of the month. The expression */30 * * * * fires every 30 minutes on the clock, which is different from a 30 minute timedelta, and Airflow also accepts None to never schedule and @once to schedule a single run.
Presets are named cron expressions. Their documented descriptions say "at midnight (24:00)" for the daily, weekly, monthly, quarterly and yearly presets, and "at the end of the hour" for @hourly. Read these with the timetable in mind: under a data interval timetable the run for a day starts after that day ends.
A preset or cron string is the shortest way to write a schedule, but it hides the timetable choice. A Dag that depends on the data interval should name its timetable instead of relying on the create_cron_data_intervals setting of whoever deploys it.
Airflow cron presets and the expressions they stand for
Airflow catchup, start date and missed intervals
By default the scheduler creates a run only for the latest interval. Airflow 3.0 made catchup_by_default False, so a Dag that doesn't set catchup will no longer backfill missed intervals. With catchup=True, the scheduler starts a run for every past interval since the start date that hasn't run, and it does the same for the gap when a paused Dag is switched on again. Several of those runs can overlap, bounded by max_active_runs_per_dag, which defaults to 16.
Leave catchup off when the Dag already works out its own backlog, for example a task that loads everything newer than the last row it saw. Turn it on when each run owns 1 interval of data and a missed interval is a hole in a table. Either way, the runs must be safe to repeat, which is the property described in idempotent pipelines.
Timedelta schedules and the clock
A cron expression produces intervals aligned to the clock boundaries it names, whatever the start date. A timedelta does not. With catchup=False, a 30 minute delta aligns to the current time: if the Dag is enabled at 01:05, the first interval runs from 00:35 to 01:05, as the timetables documentation shows. The documentation also says DeltaDataIntervalTimetable doesn't necessarily align to arbitrary bounds such as the start of a day or an hour.
Pick a cron expression when the schedule has to match the clock, such as a report at 06:00, and a delta when only the spacing matters, such as polling every 10 minutes. If a Dag has to start the moment the last one ends, @continuous does exactly that.
When you move an existing Dag from a trigger timetable to a data interval timetable, the documentation says 1 scheduled run is skipped. The reverse change doesn't skip one.
Schedule questions in a data engineering interview
Interviewers ask about the schedule because it exposes whether you know what a run represents. Common prompts: why does the run for yesterday start today, what's the data interval of a run you triggered by hand, and how would you reload 3 missed days without duplicating rows. The answers, in order: the interval ends before the run starts, a manual run has its own logical date, and each run must overwrite only its own interval.
Practice them with the Airflow interview questions and by designing the pipelines themselves in the data pipeline practice problems. The data pipeline interview questions cover the orchestration questions around them, and the system design round guide shows where scheduling comes up in the loop. For the structure of the Dag itself, see Airflow Dags explained.
Airflow schedule interval FAQ
What is the schedule interval in Apache Airflow?+
What does a cron expression mean in Airflow?+
What is the data interval in Airflow?+
What is the difference between logical date and execution date?+
Why did my daily Airflow Dag not run at the start date?+
Does Airflow backfill missed runs by default?+
How do I run an Airflow Dag every 30 minutes?+
The candidate who gets the offer
- 01
Reading a solution is not the same as writing one
Every engineer who has frozen on a query they had read a dozen times knows the gap. The only preparation that closes it is producing the answer yourself, under time, before the interview does it for you
- 02
76% of hiring managers reject on the coding task, not the resume
From HackerRank's 2024 Developer Skills Report. Candidates who look strong on paper still fail the live screen if they haven't done timed, executable practice
- 03
System design comes down to the calls you defend out loud
Ingestion, batch vs streaming, the bronze/silver/gold layers, idempotency, backfill and replay. Sketching the pipeline and naming the failure modes is the signal, not the boxes
Related guides
Airflow interview questions
Scheduling and Dag questions as interviewers ask them
Data pipeline interview questions
Pipeline design and operations questions
Data pipeline practice problems
Design problems to practice before the loop
Airflow Dag explained
How a Dag is structured and how it runs
Idempotent pipelines
Why a rerun must not double count
This week's community challenge
Dirty, production-shaped data, scored blind each week.