Data pipelines and orchestration (Apache Airflow basics)

Scripts that run on a timer will happily publish zeros when an earlier step fails. In this lesson you learn how orchestration with tools like Apache Airflow turns separate jobs into a reliable pipeline: a DAG of tasks with dependencies, retries, alerts and quality gates. You'll watch a 2 am failure recover by itself, backfill a week safely and handle data that arrives days late.

Module 4 · The Ecosystem⏱ 30 minBeginner → AdvancedRuns in your browser (Python)3 real-time scenariosMini orchestrator on your PC9 practice problems
1 The story

Monday 9 am: Sunday’s revenue is ₹0

Monday, 9 am. TiffinGo’s city managers open the revenue dashboard: Sunday shows ₹0. Kabir’s nightly scripts ran on a timer, as always: extract at 1:00, transform at 1:30, publish at 2:00. But on Sunday the extract hit a timeout and failed. The transform ran anyway on empty input, and the publish step proudly wrote zeros.

extract1:00 amvalidate1:20 amtransform1:25 ampublish2:10 amdashboard9:00 amOne DAG: each task runs only after the one before it succeedsretries · alerts · backfills · one run per day Five scripts on a timer are not a pipeline. Dependencies, retries and alerts make it one. myphatshala.in
Kabir

Sir, every script “succeeded” except the first one. The timer started each one on time, but nobody told the transform that its input was missing.

Arjun Sir

That’s the difference between scheduling and orchestration, Kabir. A timer only knows the clock. An orchestrator knows that “transform” depends on “extract”, waits for it, retries it if it fails, and alerts a human instead of publishing nonsense.

Riya

Is that what Airflow does? It’s in almost every data engineering job post.

Arjun Sir

Yes. In Apache Airflow you describe the pipeline as a DAG, a graph of tasks with arrows for dependencies, and Airflow runs it on a schedule, once per day of data, keeping a history of every run.

Kabir

And Sunday’s zeros? Can I fix them?

Arjun Sir

With a backfill: re-run the pipeline for Sunday after fixing things. If your tasks are idempotent, running them again gives the same correct result instead of duplicates. Today you’ll see retries rescue a 2 am failure, backfill a week safely, and learn why Airflow cares about a run’s logical date more than the clock.

2 Definition

What are data pipelines and orchestration?

Formal definition (write this in exams)

A data pipeline is a sequence of automated steps that moves data from sources to destinations, transforming and validating it on the way (for example extract → validate → transform → load). Orchestration is the automated coordination of those steps: scheduling runs, enforcing dependencies, passing parameters such as the logical date, handling retries and timeouts, alerting on failure and recording run history. In Apache Airflow, a pipeline is defined in Python as a DAG (directed acyclic graph) of tasks.

In simple words

A pipeline is a chain of jobs that turns raw data into something useful, like yesterday’s revenue table. An orchestrator such as Airflow runs the chain in the right order, waits when a step isn’t ready, retries steps that fail, and alerts people when something is really wrong.

Words you’ll meet in this lesson

DAGDirected acyclic graph: tasks joined by one-way arrows, with no loops.
Task / operatorOne step in a DAG; an operator is a template for a kind of step (Python, SQL, Spark, Bash).
SensorA task that waits for a condition, such as a file arriving or another DAG finishing.
ScheduleWhen runs are created, often written as cron: 0 1 * * * = 1:00 every day.
Logical dateThe period of data a run is responsible for, independent of when it actually runs.
BackfillRunning a pipeline for past logical dates.
IdempotentGives the same result however many times it is run for the same input.
Incremental loadProcessing only new or changed data since the last run.

An Airflow DAG (example)

This is what the daily orders pipeline looks like in Airflow 3 using the TaskFlow style. It is shown for reading; it needs an Airflow installation to run.

from datetime import datetime, timedelta
from airflow.sdk import dag, task      # Airflow 2.x: from airflow.decorators import dag, task

@dag(schedule="0 1 * * *",                         # every day at 1:00 am
     start_date=datetime(2026, 10, 1),
     catchup=False,
     default_args={"retries": 3, "retry_delay": timedelta(minutes=5)})
def daily_orders():

    @task
    def extract(ds=None):                           # ds = the logical date, e.g. "2026-10-19"
        return f"s3://lake/bronze/orders/date={ds}/"

    @task
    def validate(path):
        ...                                         # row counts, nulls, negative amounts
        return path

    @task
    def transform(path):
        ...                                         # submit a Spark job: bronze -> silver -> gold

    transform(validate(extract()))

daily_orders()

Pipelines and orchestration at three levels

Pick your level. New to this? Start at Beginner and climb.

Beginner A recipe, a cook and a kitchen manager

  • A pipeline is a recipe: steps in order. Buy vegetables → wash → chop → cook → serve.
  • Dependencies: you can’t chop before the vegetables arrive. In data, you can’t transform before you extract.
  • A timer (cron) is an alarm that says “start chopping at 7:30”, even if the vegetables never came.
  • An orchestrator (Airflow) is a kitchen manager: it starts each step only when the previous one is done, tries again if the shop was closed, and tells the owner if dinner will be late, instead of serving an empty plate.

Intermediate Airflow building blocks

  • DAG: the pipeline as a directed acyclic graph of tasks, written in Python, with a schedule.
  • Task / operator: one step (run SQL, call an API, submit a Spark job, run Python). Sensors wait for something, such as a file landing in S3.
  • Scheduler and executor: the scheduler decides which tasks are ready; the executor runs them on workers.
  • Logical date (data interval): each run processes one period of data, for example 19 October, even if it actually runs at 1 am on 20 October or a week later during a backfill.
  • Retries, timeouts and alerts are settings, not code you write each time.
  • Airflow orchestrates; it doesn’t crunch: heavy work runs in Spark, a warehouse or dbt; Airflow tells them when to run and checks the result.
  • Similar tools: Dagster, Prefect, Azure Data Factory, AWS Step Functions, and managed Airflow (Amazon MWAA, Google Cloud Composer, Astronomer).

Advanced Idempotency, backfills and reliability

  • Idempotent tasks: running a task twice for the same logical date gives the same result. Write by overwriting the date partition (or MERGE on keys), never by blind appends.
  • Backfill: re-run past logical dates after a bug fix or a new column. Only possible cleanly if tasks are idempotent and parameterised by date, not by “now”.
  • Incremental loads: process only new or changed data (by date partition or a watermark column such as updated_at) instead of everything every night.
  • Late data: events can arrive days late. Common fix: each run also reprocesses the last N days, which is safe because tasks are idempotent.
  • Quality gates: row-count, null and range checks between tasks stop bad data from being published (Great Expectations, dbt tests, Soda).
  • Operations: SLAs and alerts for late pipelines, lineage to see what depends on what, and keeping DAG files light so the scheduler stays fast.

Exam tip: Define a data pipeline and orchestration, draw a small DAG (extract → validate → transform → load), and explain scheduling, dependencies, retries, backfill and idempotency. Name Apache Airflow with its parts: DAG, operator, sensor, scheduler, executor.

3 Types and ways to use it

Kinds of pipelines, and cron vs an orchestrator

Batch ETL / ELT pipelines

Run on a schedule (hourly, nightly) over a period of data: extract from sources, load to the lake or warehouse, transform with Spark, SQL or dbt.

Scheduled

Streaming pipelines

Run continuously: Kafka → stream processor → sinks. Still need orchestration for deployments, backfills and checks.

Always on

Event-triggered pipelines

Start when something happens: a file lands, an upstream DAG finishes, an API is called.

On demand

ML pipelines

Prepare features, train, evaluate and deploy models on a schedule or when data drifts.

Train + serve

Cron vs an orchestrator

Cron / timer scriptsOrchestrator (Airflow, Dagster…)
DependenciesNone: each script starts by the clockTask waits for upstream tasks to succeed
FailuresSilent unless you build alertsRetries, timeouts, alerts, marked failed in the UI
BackfillsManual, error-proneBuilt in: run past logical dates
HistoryLog files, if anyEvery run, task, attempt and duration recorded
ParametersUsually “today”Logical date passed to every task
Best forOne small independent jobMulti-step pipelines across systems

Write every task as a function of the logical date: “process orders for {ds} and overwrite date={ds}”. That one habit makes retries, re-runs and backfills safe, and it is the most common thing interviewers check in pipeline design questions.

4 Step by step

Building a reliable daily pipeline

  1. Draw the DAG

    dag = {"extract": [], "validate": ["extract"], "transform": ["validate"],
           "publish": ["transform"], "email_report": ["publish"]}
    done, order = set(), []
    while len(order) < len(dag):
        for task, deps in dag.items():
            if task not in done and all(d in done for d in deps):
                order.append(task); done.add(task)
    print(" -> ".join(order))
    extract -> validate -> transform -> publish -> email_report
    Each task lists what it depends on; the orchestrator works out a valid order (a topological sort) and runs independent tasks in parallel when it can.
  2. Parameterise by logical date

    import datetime
    logical = datetime.date(2026, 10, 19)
    path = f"s3://lake/bronze/orders/date={logical}/"
    print("this run reads and writes only:", path)
    this run reads and writes only: s3://lake/bronze/orders/date=2026-10-19/
    The 19 October run reads and writes only 19 October’s partition, whether it runs at 1 am on the 20th or during a backfill next month.
  3. Extract with retries

    The source API sometimes times out at night. The extract task is set to retries=3 with a 5-minute delay (often growing each time: exponential backoff).

    Most night-time failures are temporary: a busy API, a network blip, a restarting database. Retries turn them into short delays instead of morning emergencies.
  4. Validate before publishing

    rows = [120, 340, -90, 410]
    checks = {"has rows": len(rows) > 0, "no negative amounts": all(r >= 0 for r in rows)}
    print(checks, "-> PUBLISH" if all(checks.values()) else "-> STOP and alert")
    {'has rows': True, 'no negative amounts': False} -> STOP and alert
    A quality gate between transform and publish stops impossible data (a negative amount, zero rows, a sudden 90% drop) and alerts someone. Publishing nothing is better than publishing wrong numbers.
  5. Publish idempotently

    partitions = {}
    def publish(day, revenue):
        partitions[day] = revenue          # overwrite the day’s partition
    publish("2026-10-19", 850); publish("2026-10-19", 850)   # re-run
    print(partitions, "total:", sum(partitions.values()))
    {'2026-10-19': 850} total: 850
    Overwriting the date partition means a retry or a re-run produces the same result. With appends, every re-run would add the day’s revenue again.
  6. Monitor, alert, backfill

    Airflow records every run. If Sunday failed, an alert goes out, the dashboard shows “data delayed”, and once fixed, the team clears and re-runs Sunday: a one-day backfill.

    Good pipelines are judged on the bad days: how fast failures are noticed, how clearly they are reported and how safely they are repaired.
5 See it happen

One night: what the managers see, and what Airflow does

Step through the six frames. Left: the dashboard the managers will open. Right: the DAG run behind it, including a failure and a retry.

What the managers see, and what the orchestrator does 6 steps
What you see Revenue dashboardSunSunday: updating overnight…Revenue dashboardSunSunday: updating overnight…Revenue dashboardSunSunday: updating overnight…Revenue dashboardSunSunday: updating overnight…Revenue dashboardSunSunday: ₹38.4 lakhRevenue dashboardSunFresh at 9:00 am · checks passed myphatshala.in
Behind the screen 01:00 · scheduler starts run for 2026-10-19extractrunningvalidatewaitingtransformwaitingpublishwaitingLogical date = Sunday. Extract starts; the rest wait.01:04 · extract fails: API timeoutextractfailedvalidatewaitingtransformwaitingpublishwaitingDownstream tasks do NOT start on missing input.01:09 · automatic retry after 5 minextractretry 2/3validatewaitingtransformwaitingpublishwaitingThe source is back; attempt 2 succeeds.01:25 · validate passed, transform runningextractsuccessvalidatesuccesstransformrunningpublishwaitingRow counts and amounts checked; Spark job submitted.02:10 · publish overwrote date=2026-10-19extractsuccessvalidatesuccesstransformsuccesspublishsuccessIdempotent: a re-run would give the same table.09:00 · run marked successextractsuccessvalidatesuccesstransformsuccesspublishsuccessHistory, durations and logs saved for every task. myphatshala.in
6 Real-world use

Real-time scenarios: pipelines on bad days

Three live scenarios: a failure that retries can’t fix, a week-long backfill after a bug, and an advanced look at logical dates and late-arriving data.

Real-time scenario 1

A failure that retries can’t fix

Retries fix most night-time hiccups. But on Diwali night the source database is overloaded for hours. Watch how a well-built pipeline fails safely and recovers.

Failing safely 5 steps
Animated example extractretryingvalidatewaitingtransformwaitingpublishwaitingDiwali night: source database under heavy loadExtract fails, retries at 5, 10 and 20 minutes(exponential backoff).extractfailedvalidateupstream failedtransformupstream failedpublishupstream failedAll retries used: extract FAILEDDownstream tasks are marked “upstream failed”.Nothing wrong is published.extractfailedvalidateupstream failedtransformupstream failedpublishupstream failed#data-alerts · on-call pageddaily_orders · extract failed · 02:41An alert reaches the on-call engineer with a link to logs.extractfailedvalidateupstream failedtransformupstream failedpublishupstream failedDashboard banner“Sunday data delayed, ETA 11 am”Managers see “delayed”, not “₹0”. Trust is kept.extractsuccessvalidatesuccesstransformsuccesspublishsuccess10:20 · source fixed, task cleared and re-runThe engineer clears the failed task; it and everythingdownstream re-run for Sunday. Done by 11. myphatshala.in
Real-time scenario 2

Backfilling a week after a bug fix

A bug under-counted wallet payments for seven days. The code is fixed; now the past must be repaired. Watch why idempotent tasks make a backfill safe, and appends make it a disaster.

Backfill 5 steps
Animated example Revenue (₹ lakh) published last week, with a currency-conversion bug10-133010-143310-153110-163510-173410-183810-1941A bug under-counted wallet payments for 7 days.The fix is merged. Now repair the past.Backfill: re-run the DAG for logical dates 10-13 … 10-1910-133810-144110-153910-164410-174210-184710-1951Airflow runs the fixed pipeline once per logical date,each reading and writing only its own day.If publish APPENDED rows…10-136810-147410-157010-167910-177610-188510-1992Old wrong rows + new right rows: every day doubled.Totals are now worse than before.Publish OVERWRITES each date partition10-133810-144110-153910-164410-174210-184710-1951Each day is replaced by its corrected value.Re-running again changes nothing: idempotent.Downstream tables re-run automatically10-133810-144110-153910-164410-174210-184710-1951Weekly and monthly summaries that depend on thesedays are cleared and re-run too, in dependency order. myphatshala.in
Real-time scenario 3 · advanced

Logical dates, late data and sensors

Real data doesn’t arrive on time. Orders from phones that were offline can show up days later. Watch how logical dates, a reprocessing window and a sensor keep daily numbers correct.

Time in pipelines 5 steps
Animated example Sun 00:00Mon 00:00Mon 01:00Tue 00:00Wed 00:00Thudata interval: SundayThe run for logical date Sunday covers Sunday’s data:from Sun 00:00 to Mon 00:00.Sun 00:00Mon 00:00Mon 01:00Tue 00:00Wed 00:00Thuruns Mon 01:00It actually runs Monday 1 am, once the day is complete.Tasks use the logical date, never “today”.Sun 00:00Mon 00:00Mon 01:00Tue 00:00Wed 00:00ThuTue: 1,200 late Sunday ordersPhones that were offline upload Sunday orders on Tuesday.Sunday’s published numbers are now too low.Sun 00:00Mon 00:00Mon 01:00Tue 00:00Wed 00:00ThuWed run: redo Mon–Tue + SunFix: each daily run also reprocesses the previous 3 days.Safe only because tasks overwrite their partitions.Sun 00:00Mon 00:00Mon 01:00Tue 00:00Wed 00:00Thusensor: _SUCCESS filePlus a sensor: the run waits until the source says“Sunday is complete” before extracting. myphatshala.in

Where orchestration runs every day

1. Nightly business reporting

Most companies run hundreds of nightly DAGs: copy yesterday’s data from app databases, build silver and gold tables in Spark or dbt, check quality and refresh dashboards before offices open.

App DBs→Airflow DAGs→Morning dashboards
ML

2. Machine-learning retraining

Recommendation and fraud models are retrained on a schedule: build features, train, evaluate against the current model, and deploy only if better, all as one orchestrated pipeline.

Features→Train → evaluate→Deploy if better
RBI / GST

3. Regulatory and finance reports

Banks, NBFCs and e-commerce firms produce daily and monthly regulatory and tax reports with strict deadlines. Orchestrators track SLAs and alert long before a deadline is missed.

Ledgers→Checked pipeline→Filed on time

4. Keeping streaming systems healthy

Even real-time platforms need scheduled jobs: compacting small files, reprocessing late data, rebuilding aggregates, and running data-quality checks on stream outputs.

Stream output→Scheduled maintenance→Clean tables
7 Run the code

A pipeline as a DAG, run in order

The pipeline is a dictionary of tasks and the tasks each depends on. The program finds a valid order (a topological sort), runs each task for one logical date, retries a flaky step, and stops downstream tasks if something fails for good. Then try the challenges: parallel branches, quality gates, idempotent publishing, backfills and incremental loads.

dag = {"extract": [], "validate": ["extract"], "transform": ["validate"], "publish": ["transform"]}
attempts_needed = {"extract": 2}            # extract fails once, then works
RETRIES = 3

def order(dag):
    done, out = set(), []
    while len(out) < len(dag):
        for t, deps in dag.items():
            if t not in done and all(d in done for d in deps):
                out.append(t); done.add(t)
    return out

logical_date = "2026-10-19"
for task in order(dag):
    for attempt in range(1, RETRIES + 1):
        if attempt >= attempts_needed.get(task, 1):
            print(f"{logical_date} {task:9} success (attempt {attempt})")
            break
        print(f"{logical_date} {task:9} failed, retrying")
    else:
        print(f"{task} failed for good: downstream skipped, alert sent")
        break
2026-10-19 extract failed, retrying 2026-10-19 extract success (attempt 2) 2026-10-19 validate success (attempt 1) 2026-10-19 transform success (attempt 1) 2026-10-19 publish success (attempt 1)
  • dag = {"extract": [], "validate": ["extract"], "transform": ["validate"], "publish": ["transform"]}Each task lists its upstream tasks. That is all an orchestrator needs to know the order.
  • if t not in done and all(d in done for d in deps):A task is ready only when every task it depends on has finished.
  • for attempt in range(1, RETRIES + 1):Retries handle temporary failures such as timeouts without waking anyone up.
  • else:The for … else runs only if no attempt succeeded: stop the pipeline instead of running on missing data.

Run it on your PC: a tiny orchestrator with retries and backfills

This program defines a four-task DAG, runs it in dependency order for a logical date, retries a flaky extract step, and publishes one JSON file per day into a warehouse folder. Then it backfills three days twice to prove that re-running is safe. Only the standard library is used. Save it as mini_orchestrator.py and run it.

DAG order, retries, idempotent backfill

"""A tiny orchestrator: tasks with dependencies, run in order, with retries,
one output partition per logical date, and a backfill that re-runs safely."""
import json, os, random

random.seed(1)
os.makedirs("warehouse", exist_ok=True)

ORDERS = {  # pretend source system: orders per day
    "2026-10-17": [120, 340, 90], "2026-10-18": [200, 150], "2026-10-19": [410, 80, 60, 300],
}
DAG = {"extract": [], "validate": ["extract"], "transform": ["validate"], "publish": ["transform"]}


def topo_order(dag):
    done, order = set(), []
    while len(order) < len(dag):
        for task, deps in dag.items():
            if task not in done and all(d in done for d in deps):
                order.append(task)
                done.add(task)
    return order


def run_task(task, day, state, flaky):
    if task == "extract":
        if flaky and random.random() < 0.6:      # the source API sometimes times out
            raise TimeoutError("source API timed out")
        state["rows"] = ORDERS[day]
    elif task == "validate":
        assert all(a > 0 for a in state["rows"]), "negative amount"
    elif task == "transform":
        state["summary"] = {"day": day, "orders": len(state["rows"]), "revenue": sum(state["rows"])}
    elif task == "publish":
        # idempotent: overwrite this day's partition instead of appending
        with open(f"warehouse/daily_{day}.json", "w") as f:
            json.dump(state["summary"], f)


def run_dag(day, flaky=False, retries=3, verbose=True):
    state = {}
    for task in topo_order(DAG):
        for attempt in range(1, retries + 1):
            try:
                run_task(task, day, state, flaky)
                if verbose:
                    print(f"  {day} {task:9} ok" + (f" (attempt {attempt})" if attempt > 1 else ""))
                break
            except Exception as e:
                print(f"  {day} {task:9} failed: {e}" + ("; retrying" if attempt < retries else "; giving up"))
        else:
            print(f"  {day} downstream tasks skipped, alert sent")
            return
    if not verbose:
        print(f"  {day} all tasks ok -> {state['summary']}")


print("Task order:", topo_order(DAG))
print("\nNightly run for 2026-10-19 (flaky source):")
run_dag("2026-10-19", flaky=True)

print("\nBackfill 2026-10-17 to 2026-10-19 (run twice to prove it is safe):")
for run in (1, 2):
    print(f" run {run}:")
    for day in ORDERS:
        run_dag(day, verbose=False)
files = sorted(os.listdir("warehouse"))
total = sum(json.load(open("warehouse/" + f))["revenue"] for f in files)
print(f"\nPartitions in warehouse: {files}")
print(f"Total revenue across partitions: Rs {total} (not doubled, because publish overwrites)")

# In a terminal:  python mini_orchestrator.py
Task order: ['extract', 'validate', 'transform', 'publish'] Nightly run for 2026-10-19 (flaky source): 2026-10-19 extract failed: source API timed out; retrying 2026-10-19 extract ok (attempt 2) 2026-10-19 validate ok 2026-10-19 transform ok 2026-10-19 publish ok Backfill 2026-10-17 to 2026-10-19 (run twice to prove it is safe): run 1: 2026-10-17 all tasks ok -> {'day': '2026-10-17', 'orders': 3, 'revenue': 550} 2026-10-18 all tasks ok -> {'day': '2026-10-18', 'orders': 2, 'revenue': 350} 2026-10-19 all tasks ok -> {'day': '2026-10-19', 'orders': 4, 'revenue': 850} run 2: 2026-10-17 all tasks ok -> {'day': '2026-10-17', 'orders': 3, 'revenue': 550} 2026-10-18 all tasks ok -> {'day': '2026-10-18', 'orders': 2, 'revenue': 350} 2026-10-19 all tasks ok -> {'day': '2026-10-19', 'orders': 4, 'revenue': 850} Partitions in warehouse: ['daily_2026-10-17.json', 'daily_2026-10-18.json', 'daily_2026-10-19.json'] Total revenue across partitions: Rs 1750 (not doubled, because publish overwrites)

Recorded from a real run with Python 3.13; the random seed makes it repeatable. Open the warehouse folder after running: there is exactly one file per day, even though the backfill ran twice. To try real Airflow, install it with Docker using the official quick-start, or use a managed service, and put the DAG shown in the Definition section into its dags folder.

8 Line by line

Finding the run order, one line at a time

Three tasks are listed out of order. Watch the loop pick a task only when everything it depends on is already done.

Worked examples: real problems, solved and traced in memory

Four pipeline problems, from ordering tasks to sizing a backfill. Each runs live and is traced.

Example 1BeginnerScheduling · cron

Read a cron schedule

Problem statement

Airflow schedules are often cron expressions with five fields: minute, hour, day of month, month, day of week. Explain three schedules in words.

Input
0 1 * * * · 30 */4 * * * · 0 9 * * 1
Expected output
0 1 * * *     -> minute 0, hour 1, every day
30 */4 * * *  -> minute 30, every 4 hours, every day
0 9 * * 1     -> minute 0, hour 9, on Mondays

Approach

  1. Split each expression into its five fields.
  2. Translate the minute and hour fields; * means every, */4 means every 4.
  3. Day of week 1 means Monday.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

Example 2IntermediatePipeline design · dependencies

Which tasks can run in parallel?

Problem statement

A DAG has a common extract, two independent cleaning branches and a final report that needs both. Group tasks into waves that can run at the same time, and find the shortest total time if each task takes the given minutes.

Input
extract 10, clean_orders 20, clean_riders 5, report 8 (minutes)
Expected output
wave: ['extract']
wave: ['clean_orders', 'clean_riders']
wave: ['report']
shortest total: 38 minutes (vs 43 one by one)

Approach

  1. A task can start when all its upstream tasks are done.
  2. Each wave takes as long as its slowest task.
  3. Total time = sum of wave times.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

Example 3IntermediateReliability · retries

How long can retries delay a pipeline?

Problem statement

A task retries up to 4 times with exponential backoff starting at 2 minutes (2, 4, 8, 16). Each attempt takes 3 minutes. What is the worst-case time before it finally fails, and does it still meet a 2-hour window?

Input
4 retries, backoff 2 min doubling, 3 min per attempt
Expected output
delays between attempts: [2, 4, 8, 16]
worst case before failing: 45 minutes
fits a 2-hour window

Approach

  1. There is one first attempt plus 4 retries: 5 attempts.
  2. Add the waiting time between attempts: 2 + 4 + 8 + 16.
  3. Compare the total with the time window.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

Example 4AdvancedOperations · backfill

Plan a safe backfill

Problem statement

A bug affected 45 days. Each daily run takes 12 minutes, the cluster can run 6 days in parallel without hurting nightly jobs, and runs must finish before the 1 am nightly start, with 6 hours available. Plan the backfill.

Input
45 days, 12 min per run, 6 parallel, 6 hours
Expected output
batches: 8 | minutes needed: 96
nights required: 1

Approach

  1. Batches = days ÷ parallel runs, rounded up.
  2. Time = batches × minutes per run.
  3. If it doesn't fit, split across nights.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

9 Quiz and revision

Check yourself

Quick revision

Lesson 4.5 in one screen

  • Pipeline = automated steps from source to destination. Orchestration = scheduling + dependencies + retries + alerts + history.
  • Airflow: DAG of tasks; operators (do work), sensors (wait), scheduler (decides), executor (runs).
  • Cron 0 1 * * * = 1:00 daily. Each run has a logical date / data interval.
  • Idempotent tasks: overwrite date partitions or MERGE; never blind appends.
  • Backfill past dates safely only if tasks are idempotent and use the logical date, not now().
  • Quality gates before publish; alerts and “data delayed” instead of wrong numbers.
  • Incremental loads (watermarks), reprocessing windows for late data; Airflow orchestrates, Spark/SQL/dbt do the heavy work.

Interview and viva questions

What is a data pipeline?

A data pipeline is an automated sequence of steps that moves data from one or more sources to destinations, typically extracting, validating, transforming and loading it, so that it is available in a usable form (for example, a daily revenue table for dashboards). Pipelines can be batch (scheduled) or streaming (continuous).

What is Apache Airflow and what are its main components?

Apache Airflow is an open-source platform for authoring, scheduling and monitoring workflows as DAGs written in Python. Its main components are the scheduler (creates runs and decides which tasks are ready), the executor and workers (run tasks), the metadata database (stores run and task state), the web server/UI (monitoring and manual actions), and DAG files containing tasks built from operators and sensors.

What does idempotent mean in data pipelines and why is it important?

An idempotent task produces the same result no matter how many times it runs for the same input or logical date. It is important because tasks are retried after failures, re-run manually and backfilled; without idempotency these would duplicate or corrupt data. Common techniques are overwriting date partitions, MERGE/upsert on keys and deduplication.

What is the difference between scheduling and orchestration?

Scheduling only starts jobs at given times. Orchestration also manages dependencies between tasks, passes parameters such as logical dates, retries failures, enforces timeouts and SLAs, alerts on problems, records history and supports backfills across many systems.

How do you handle late-arriving data in batch pipelines?

Process data by event date into date partitions, and have each run reprocess a window of recent days (for example the last 3 days) using idempotent overwrites or MERGE, so late records are included. Use sensors or completeness markers to wait for sources, and monitor how late data typically arrives to choose the window.

Practice problems

Pipeline and orchestration exercises. The last two are business tasks.

P1Easy

Upstream tasks

Print the upstream tasks of “report”.

Input
DAG of 4 tasks
Expected output
['clean', 'enrich']
Show solution and explanation
dag = {"extract": [], "clean": ["extract"], "enrich": ["extract"], "report": ["clean", "enrich"]}
print(dag["report"])
['clean', 'enrich']

How it works: The report waits for both branches.

P2Easy

Partition path for a date

Build the output path for logical date 2026-10-19.

Input
2026-10-19
Expected output
s3://lake/gold/revenue/date=2026-10-19/
Show solution and explanation
ds = "2026-10-19"
print(f"s3://lake/gold/revenue/date={ds}/")
s3://lake/gold/revenue/date=2026-10-19/

How it works: Every run writes only its own date partition.

P3Easy

Runs per year

How many runs does a DAG scheduled every 4 hours create in a year?

Input
every 4 hours
Expected output
2190
Show solution and explanation
print(24 // 4 * 365)
2190

How it works: 6 runs a day for 365 days.

P4Medium

Failed tasks and their victims

If “clean” fails, print every task that can't run (all its downstream tasks).

Input
DAG of 5 tasks
Expected output
['enrich', 'report']
Show solution and explanation
dag = {"extract": [], "clean": ["extract"], "enrich": ["clean"], "report": ["enrich"], "audit": ["extract"]}
failed = {"clean"}
changed = True
while changed:
    changed = False
    for t, deps in dag.items():
        if t not in failed and any(d in failed for d in deps):
            failed.add(t); changed = True
print(sorted(failed - {"clean"}))
['enrich', 'report']

How it works: Failure spreads down the DAG; “audit” still runs because it doesn't depend on “clean”.

P5Medium

Watermark load

Load rows with id greater than the last loaded id (42) and print the new watermark.

Input
ids [40, 41, 43, 44, 47]
Expected output
load: [43, 44, 47] | new watermark: 47
Show solution and explanation
ids = [40, 41, 43, 44, 47]
last = 42
new = [i for i in ids if i > last]
print("load:", new, "| new watermark:", max(new))
load: [43, 44, 47] | new watermark: 47

How it works: Incremental loads read only new data, which keeps nightly runs short.

P6Advanced

Detect a DAG cycle

A DAG must have no cycles. Print whether the graph has one.

Input
a → b → c → a
Expected output
cycle!
Show solution and explanation
graph = {"a": ["b"], "b": ["c"], "c": ["a"]}
def has_cycle(g):
    visiting, done = set(), set()
    def visit(n):
        if n in visiting:
            return True
        if n in done:
            return False
        visiting.add(n)
        found = any(visit(m) for m in g[n])
        visiting.discard(n); done.add(n)
        return found
    return any(visit(n) for n in g)
print("cycle!" if has_cycle(graph) else "no cycle")
cycle!

How it works: Airflow refuses DAGs with cycles, because no valid order exists.

P7Advanced

Reprocessing window

Each daily run reprocesses the last 3 days. For run date 2026-10-19, print the dates it overwrites.

Input
2026-10-19, window 3
Expected output
['2026-10-19', '2026-10-18', '2026-10-17']
Show solution and explanation
import datetime
run = datetime.date(2026, 10, 19)
print([str(run - datetime.timedelta(days=d)) for d in range(3)])
['2026-10-19', '2026-10-18', '2026-10-17']

How it works: Late data from the previous two days is picked up safely because each day is overwritten.

P8Business problem

Meeting the 8 am SLA

The pipeline starts at 1:00. Tasks take 25, 40, 70 and 15 minutes in sequence. Worst case, extract also needs 2 retries of 25 minutes each with 10-minute waits. Does it finish by 8:00?

Input
25, 40, 70, 15 min
Expected output
normal: done at 3:30 OK
worst: done at 4:40 OK
Show solution and explanation
normal = 25 + 40 + 70 + 15
worst = normal + 2 * (25 + 10)
for name, m in [("normal", normal), ("worst", worst)]:
    end = 60 + m          # minutes after midnight
    print(f"{name}: done at {end // 60}:{end % 60:02d}", "OK" if end <= 8 * 60 else "LATE")
normal: done at 3:30 OK worst: done at 4:40 OK

How it works: SLA planning uses the worst case, including retries.

P9Business problem

Cost of a wrong dashboard

A wrong revenue number leads to 3 hours of investigation by 5 people at Rs 1,500 per hour each, about twice a month. A quality-gate project costs Rs 1.2 lakh once. How many months to pay back?

Input
5 × 3 h × Rs 1,500 × 2 per month
Expected output
cost per month: 45000
payback months: 2.7
Show solution and explanation
monthly_cost = 5 * 3 * 1500 * 2
print("cost per month:", monthly_cost)
print("payback months:", round(1_20_000 / monthly_cost, 1))
cost per month: 45000 payback months: 2.7

How it works: Quality gates are cheap compared with the cost of decisions made on wrong data.

Real-world scenario questions (asked in company interviews)

Pipeline and orchestration questions from real teams and interviews. Decide your answer first, then open the model answer.

Pipeline designFresher

Design a daily sales pipeline

Orders live in a PostgreSQL database. Finance wants yesterday’s revenue by city on a dashboard by 8 am every day.

The interviewer is checking: can you design a simple, reliable, orchestrated pipeline?

Show a strong answer
  1. Airflow DAG scheduled at 1 am, parameterised by the logical date.
  2. Extract yesterday’s orders (by updated_at) to the lake as bronze/orders/date=….
  3. Validate: row count vs the source, no negative amounts, no null cities.
  4. Transform to a gold table revenue_by_city, overwriting that date’s partition.
  5. Publish to the dashboard; on failure, retries then an alert and a “delayed” banner.
  6. Add an SLA alert if not done by 7 am.

Avoid: a cron job per script with no dependencies or checks.

Reliability · idempotency1–3 years

Re-running a job doubled the numbers

A failed job was re-run manually and the monthly revenue report now shows twice the real amount for that day.

The interviewer is checking: do you understand idempotency and how to make pipelines safe to re-run?

Show a strong answer

The load step appends rows, so each run adds the day again. Make the task idempotent: write to a date partition and overwrite it (INSERT OVERWRITE or replace the folder), or use MERGE on a unique key. Deduplicate the affected day, then re-run once more to prove nothing changes. Add a check that compares row counts with the source.

Avoid: asking people to “be careful not to run it twice”.

Airflow · concepts1–3 years

Why does the 19 Oct run happen on 20 Oct?

A new teammate is confused: the DAG run labelled 2026-10-19 started at 1 am on 20 October, and their task that uses datetime.now() processed the wrong day.

The interviewer is checking: do you understand logical dates and data intervals?

Show a strong answer

In Airflow, a run’s logical date marks the start of the data interval it processes. A daily run for 19 October can only start after the interval ends, so it runs on 20 October. Tasks must use the run’s logical date (ds, data_interval_start) rather than now(), so that retries and backfills process the intended day.

Avoid: changing the schedule to run “earlier” or hard-coding yesterday’s date.

Architecture · toolsFresher

“Should Spark run inside Airflow?”

A teammate wants to load a 200 GB dataset into a Python task inside Airflow because “Airflow already runs Python”.

The interviewer is checking: do you know what an orchestrator is for?

Show a strong answer

No. Airflow should coordinate work, not do heavy processing. Its workers are sized for many small tasks, and big in-memory work there will crash workers and slow the scheduler. Use Airflow to submit a Spark job (on Databricks, EMR, Dataproc or Kubernetes), a warehouse SQL statement or a dbt run, wait for it, check the result and move on.

Avoid: scaling Airflow workers to hundreds of GB of RAM to process data in tasks.