Airflow Interview Questions for Data Engineers
Airflow interviews probe the behaviour that surprises teams in production. Five questions are answered by running a real Airflow 3 pipeline on household power data, including a validation failure that Airflow marked as a successful run, an empty data interval, and the documented fix for both.
GloryTecks Team
Training & Placement Faculty
Last updated: 9 October 2026
Airflow interview questions for data engineers go beyond "what is a DAG?". Interviewers ask about the behaviour that surprises teams in production: which date a run is for, what happens downstream of a failure, and how Airflow decides whether a run succeeded. The answers below come from running a real Airflow 3 pipeline on one-minute household electricity readings (UCI: household power consumption). It ran once for a normal day, once for 20 March 2010, when 84% of the readings are missing, and once as a rerun. One of the results is a failure that Airflow reported as a success.
import glob
import os
from datetime import datetime, timedelta, timezone
import pandas as pd
from airflow.exceptions import AirflowFailException
from airflow.sdk import dag, task
LANDING, WAREHOUSE = "landing", "warehouse" # one CSV per day arrives in landing/
def build(dag_id, fixed):
def on_failure(context): # the fixed version alerts from a callback
open("alerts.log", "a").write(f"{dag_id} failed\n")
@dag(dag_id=dag_id, schedule="@daily", start_date=datetime(2010, 3, 1, tzinfo=timezone.utc), catchup=False,
default_args={"retries": 2, "retry_delay": timedelta(minutes=5)}, on_failure_callback=on_failure if fixed else None)
def power_daily():
@task
def extract(logical_date=None, data_interval_start=None, data_interval_end=None):
day = logical_date.strftime("%Y-%m-%d") # the date this run is for
rows = pd.read_csv(f"{LANDING}/{day}.csv")
with open(f"interval_{dag_id}_{day}.txt", "w") as f:
f.write(f"{data_interval_start:%Y-%m-%d %H:%M} -> {data_interval_end:%Y-%m-%d %H:%M}")
return {"day": day, "rows": len(rows), "missing": int(rows.Global_active_power.isna().sum())}
@task
def validate(batch):
if batch["missing"] > 0.1 * batch["rows"]: # retrying will not bring readings back
raise AirflowFailException(f"{batch['day']}: {batch['missing']} of {batch['rows']} readings missing")
return batch
@task
def load(batch):
out = f"{WAREHOUSE}/day={batch['day']}"
os.makedirs(out, exist_ok=True)
pd.read_csv(f"{LANDING}/{batch['day']}.csv").to_csv(f"{out}/part.csv", index=False) # overwrite: reruns are safe
return batch["day"]
@task
def meter_total(day, meter): # one mapped task instance per sub-meter
return float(pd.read_csv(f"{WAREHOUSE}/day={day}/part.csv")[f"Sub_metering_{meter}"].sum())
@task(trigger_rule="all_done")
def cleanup():
for f in glob.glob("tmp_*"):
os.remove(f)
checked = validate(extract())
totals = meter_total.partial(day=load(checked)).expand(meter=[1, 2, 3])
if fixed:
totals >> cleanup().as_teardown() # teardowns do not decide the run's state
else:
@task(trigger_rule="one_failed")
def alert():
open("alerts.log", "a").write(f"{dag_id} alert task ran\n")
[checked, totals] >> alert()
totals >> cleanup()
return power_daily()
naive, fixed = build("power_daily", fixed=False), build("power_daily_fixed", fixed=True)
if __name__ == "__main__":
raw = pd.read_csv("household_power_consumption.txt", sep=";", na_values="?") # UCI household power
raw["day"] = pd.to_datetime(raw.Date, format="%d/%m/%Y").dt.strftime("%Y-%m-%d")
os.makedirs(LANDING, exist_ok=True)
for d in ("2010-03-19", "2010-03-20"):
raw[raw.day == d].drop(columns="day").to_csv(f"{LANDING}/{d}.csv", index=False)
for pipeline, d in ((naive, "2010-03-19"), (naive, "2010-03-20"), (naive, "2010-03-19"), (fixed, "2010-03-20")):
run = pipeline.test(logical_date=datetime.fromisoformat(d).replace(tzinfo=timezone.utc))
states = {(ti.task_id if ti.map_index < 0 else f"{ti.task_id}[{ti.map_index}]"): ti.state for ti in run.get_task_instances()}
rows = sum(len(pd.read_csv(f)) for f in glob.glob(f"{WAREHOUSE}/day=*/part.csv"))
print(f"{pipeline.dag_id} {d}: run {run.state} | data interval {open(f'interval_{pipeline.dag_id}_{d}.txt').read()} | warehouse rows {rows:,}")
print(" ", ", ".join(f"{k}={v}" for k, v in sorted(states.items())))
print("alerts.log:", open("alerts.log").read().strip().replace("\n", " | "))power_daily 2010-03-19: run success | data interval 2010-03-19 00:00 -> 2010-03-19 00:00 | warehouse rows 1,440
alert=skipped, cleanup=success, extract=success, load=success, meter_total[0]=success, meter_total[1]=success, meter_total[2]=success, validate=success
power_daily 2010-03-20: run success | data interval 2010-03-20 00:00 -> 2010-03-20 00:00 | warehouse rows 1,440
alert=success, cleanup=success, extract=success, load=upstream_failed, meter_total[0]=upstream_failed, meter_total[1]=upstream_failed, meter_total[2]=upstream_failed, validate=failed
power_daily 2010-03-19: run success | data interval 2010-03-19 00:00 -> 2010-03-19 00:00 | warehouse rows 1,440
alert=skipped, cleanup=success, extract=success, load=success, meter_total[0]=success, meter_total[1]=success, meter_total[2]=success, validate=success
power_daily_fixed 2010-03-20: run failed | data interval 2010-03-20 00:00 -> 2010-03-20 00:00 | warehouse rows 1,440
cleanup=success, extract=success, load=upstream_failed, meter_total[0]=upstream_failed, meter_total[1]=upstream_failed, meter_total[2]=upstream_failed, validate=failed
alerts.log: power_daily alert task ran | power_daily_fixed failedA1. What is the difference between the logical date and the data interval?
The logical date identifies the run; the data interval is the period of data it should process. For a classic daily schedule, a run covers midnight to midnight and starts after the interval ends. But every run here reported an empty interval, start equal to end, such as 19 March 00:00 to 19 March 00:00. In Airflow 3, a manual run's data interval depends on the timetable and need not match the logical date, and bare cron schedules can default to a timetable without data intervals (Airflow: upgrading to Airflow 3). The documented advice is to use logical_date explicitly when you need the run's date, which is what this DAG does.
What to say: know which dates your tasks rely on, and do not port Airflow 2 code that reads data_interval_start without checking.
A2. What happens to tasks downstream of a failure?
By default, a task runs only if all its upstream tasks succeeded. When validation failed on 20 March, load and the three mapped meter_total tasks became upstream_failed and never ran. Trigger rules change this: the alert task with one_failed ran because something failed, and cleanup with all_done ran regardless. Validation raised AirflowFailException, so the day's missing data was not retried pointlessly.
A3. How does Airflow decide whether a DAG run succeeded?
From the leaf tasks, the ones with no downstream tasks. The run succeeds if every leaf succeeded or was skipped (Airflow: Dag runs). That produced the surprise here: the 20 March run was marked success, although validation failed and nothing was loaded, because its leaves, alert and cleanup, both succeeded. Any dashboard or downstream sensor watching run states would have seen a green run, and the missing day would only have been noticed when someone looked at the numbers.
The fix that ran: make cleanup a teardown task, which is ignored when evaluating run state (Airflow: setup and teardown), and move the alert to an on_failure_callback. The fixed DAG's run for 20 March was marked failed, and the callback still wrote the alert.
A4. What is dynamic task mapping?
Creating one task instance per input at run time with expand(), while partial() sets the shared arguments (Airflow: dynamic task mapping). Here, one meter_total instance ran for each of the three sub-meters, shown as meter_total[0] to [2]. In practice the list often comes from an upstream task, for example one instance per file that arrived.
A5. How do you make reruns safe?
Make each run write a deterministic location for its date, overwriting rather than appending. The load step wrote warehouse/day=2010-03-19/part.csv and overwrote it on the rerun, so the warehouse held 1,440 rows, one per minute of that day, before and after. An appending load would have doubled them. Reruns are routine in Airflow: clearing a task, backfilling a range and retries all run tasks again.
Other questions to expect
- What is catchup? With
catchup=True, the scheduler creates runs for every interval between the start date and now that has not run yet; with False, only the latest. - Sensors? Tasks that wait for a condition, such as a file. Use deferrable or reschedule mode so a waiting sensor does not hold a worker slot.
- XComs? Small values passed between tasks; pass references to data, such as paths, not the data itself.
- Pools and concurrency? Pools cap how many tasks can use a shared resource at once, such as a database that tolerates only five connections;
max_active_runsandmax_active_taskslimit how much of one DAG runs in parallel, which matters most during a large backfill.
How to answer Airflow questions
- Know which date drives each task, and say it explicitly.
- Think about leaves when you add alerting or cleanup tasks.
- Design every task to be rerun.
The basics are in the Airflow tutorial, ML scheduling in Airflow for ML pipelines, and tool choice in Airflow vs Dagster vs Prefect. Load design is covered in ETL interview questions.
What are common Airflow interview questions?
Why does my Airflow run show success when a task failed?
What changed about dates in Airflow 3?
How do I make an Airflow task idempotent?
Pipeline orchestration is part of the Data Engineering course.
Sources
- Dag Runs (Apache Airflow documentation) — The Apache Software Foundation
- Setup and teardown (Apache Airflow documentation) — The Apache Software Foundation
- Upgrading to Airflow 3 (Apache Airflow documentation) — The Apache Software Foundation
- Dynamic Task Mapping (Apache Airflow documentation) — The Apache Software Foundation
- Individual Household Electric Power Consumption (UCI Machine Learning Repository) — UCI Machine Learning Repository
GloryTecks Team
Training & Placement Faculty
The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.
Want to master Interview Questions?
Join GloryTecks’ expert-led Data Engineering training in Hyderabad with real-time projects and 100% placement support.
Related articles
Python Interview Questions for Data Roles
The plain-Python coding tasks data-analyst and data-engineer interviews use, without pandas: counting, deduplicating, grouping, streaming large files, flattening JSON, joining, and the bugs interviewers plant. Each has a worked solution and the follow-up questions to expect.
Data Science Interview Questions and Answers 2026
A data science interview is several interviews in one: statistics, machine learning, coding, product sense and a take-home. This guide maps each round with model answers, then works a churn take-home on real telecom data, including the checks that separate a strong submission from a high score.
AWS Interview Questions for Data Engineers
AWS interview questions for data engineers on S3, Glue, Athena, Redshift, Kinesis, Step Functions and security, each with the points a strong answer includes, the follow-up to expect, and three end-to-end scenario questions.