Lesson 6 · Scheduling & execution
Catchup & backfill
Running (or deliberately not running) missed intervals.
Your win: distinguish catchup from backfill, and explain why
catchup=False is almost always the right default — a favourite "what went
wrong?" interview scenario.
Catchup: the scheduler fills in the past
When you enable a DAG whose start_date is in the past, what happens to all
the intervals between then and now? That's catchup. With
catchup=True, the scheduler creates a DAG run for every missed
interval. With catchup=False, it runs only the latest
one.1
start_date two years ago + catchup=True + a daily schedule =
~730 DAG runs kick off the instant you enable the DAG, hammering your
warehouse. This is why catchup=False is the safe default — turn it on only
when you genuinely want to process history.
catchup=False
(airflow/dags/templates/common-spark-job.py:24) — and it
must, because start_date is hard-coded to
datetime(2024, 1, 1). With catchup=True, enabling any DAG would
trigger years of backfilled runs. No template opts into catchup.
Backfill: you run the past on purpose
Backfill is the flip side: you deliberately (re)run a DAG over a specific past date range, via the CLI or API — for reprocessing after a bug fix, or running a brand-new pipeline over historical data.2
Airflow — DAG Runs (catchup) + Backfill
The official catchup behaviour and the (Airflow 3) dedicated backfill page.
→ core-concepts — DAG Runs / catchup
→ core-concepts — Backfill
Check yourself (from memory)
Q1. catchup=True makes the scheduler…
catchup=False runs only the latest.
Q2. Our DAGs set catchup=False to…
start_date=2024-01-01, catchup=True
would backfill years of runs on enable. False = only the latest.
Q3. Backfill is used to…
catchup=False is the usual choice.start_date when a DAG is enabled. With catchup=True
and a past start_date, enabling the DAG triggers a flood of historical runs — so
catchup=False (our default everywhere) runs only the latest, which you almost
always want. BACKFILL: YOU deliberately (re)running a past date range via CLI/API — for
reprocessing after a bug fix or a new pipeline over old data. Same "run past intervals"
outcome; catchup is automatic-on-enable, backfill is on-demand. Our templates hard-code
start_date=2024-01-01, so catchup=True would be catastrophic.start_date (e.g. now()) is a bug? Ask me.