Dynamic task generation with loops in Apache Airflow - Time & Space Complexity
When we create tasks dynamically in Airflow using loops, the number of tasks depends on the loop size.
We want to understand how the total work grows as we add more tasks.
Analyze the time complexity of the following code snippet.
from airflow import DAG
from airflow.operators.bash import BashOperator
from datetime import datetime
def generate_tasks(dag, task_count):
for i in range(task_count):
BashOperator(
task_id=f'task_{i}',
bash_command='echo Hello',
dag=dag
)
with DAG('example_dag', start_date=datetime(2024, 1, 1)) as dag:
generate_tasks(dag, 5)
This code creates a number of BashOperator tasks inside a DAG using a loop.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Loop creating tasks
- How many times: Equal to
task_counttimes
Each new task adds one more operation to create and register it.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 10 task creations |
| 100 | 100 task creations |
| 1000 | 1000 task creations |
Pattern observation: The work grows directly with the number of tasks.
Time Complexity: O(n)
This means the time to create tasks grows linearly as you add more tasks.
[X] Wrong: "Creating tasks in a loop is instant no matter how many tasks there are."
[OK] Correct: Each task creation takes some time, so more tasks mean more total time.
Understanding how loops affect task creation helps you explain how Airflow scales with many tasks.
"What if we nested loops to create tasks inside tasks? How would the time complexity change?"
Practice
Solution
Step 1: Understand dynamic task generation
Dynamic task generation uses loops to create many tasks without writing each one manually.Step 2: Identify the key benefit
Each task must have a unique task_id, and loops help generate these automatically, keeping the DAG clean.Final Answer:
It creates multiple tasks with unique IDs automatically. -> Option BQuick Check:
Loops generate unique tasks = C [OK]
- Thinking loops schedule tasks differently
- Confusing task creation with scheduling
- Assuming loops reduce DAG files only
Solution
Step 1: Recall TaskFlow API dynamic task syntax
To create dynamic tasks, use the override method with unique task_id inside the loop and call the function.Step 2: Check each option
for i in range(3): task = task_function.override(task_id=f'task_{i}')() correctly uses override with unique task_id and calls the function. Others miss calling or unique IDs.Final Answer:
for i in range(3): task = task_function.override(task_id=f'task_{i}')() -> Option CQuick Check:
Use override + unique task_id + call = A [OK]
- Not calling the function after override
- Using same task_id for all tasks
- Missing override method
tasks = []
for day in ['mon', 'tue', 'wed']:
task = BashOperator(
task_id=f'print_{day}',
bash_command=f'echo {day}'
)
tasks.append(task)Solution
Step 1: Analyze the loop and task_id
The loop iterates over ['mon', 'tue', 'wed'], creating a task with task_id 'print_' plus the day string.Step 2: Identify the task IDs created
Tasks created are 'print_mon', 'print_tue', and 'print_wed'.Final Answer:
Tasks with IDs: print_mon, print_tue, print_wed -> Option AQuick Check:
Loop days + task_id f-string = D [OK]
- Assuming numeric indexes used in task_id
- Confusing variable name with string literal
- Thinking syntax error occurs here
for i in range(3):
task = BashOperator(
task_id='task_i',
bash_command='echo Hello'
)Solution
Step 1: Check task_id uniqueness
The task_id is set as the literal string 'task_i' for all tasks, not using the loop variable i.Step 2: Understand impact of non-unique task_id
Airflow requires unique task_ids; repeating 'task_i' causes conflicts or errors.Final Answer:
task_id is not unique for each task -> Option AQuick Check:
Unique task_id needed = B [OK]
- Thinking bash_command is wrong
- Missing calling BashOperator (it's a class, needs parentheses)
- Assuming range usage is incorrect
Solution
Step 1: Understand dependency chaining
To run tasks sequentially, each task must depend on the previous one using '>>' operator.Step 2: Analyze each option
prev_task = None for file in ['a.txt', 'b.txt', 'c.txt', 'd.txt']: task = BashOperator(task_id=f'process_{file}', bash_command=f'cat {file}') if prev_task: prev_task >> task prev_task = task correctly sets prev_task >> task and updates prev_task. for file in ['a.txt', 'b.txt', 'c.txt', 'd.txt']: task = BashOperator(task_id='process_file', bash_command=f'cat {file}') task.set_upstream(prev_task) uses undefined prev_task. tasks = [] for file in ['a.txt', 'b.txt', 'c.txt', 'd.txt']: task = BashOperator(task_id=f'process_{file}', bash_command=f'cat {file}') tasks.append(task) tasks[0] >> tasks[1] >> tasks[2] chains after loop, but tasks list is not linked sequentially. prev_task = None for file in ['a.txt', 'b.txt', 'c.txt', 'd.txt']: task = BashOperator(task_id=f'process_{file}', bash_command=f'cat {file}') task >> prev_task prev_task = task reverses dependency causing wrong order.Final Answer:
prev_task = None for file in ['a.txt', 'b.txt', 'c.txt', 'd.txt']: task = BashOperator(task_id=f'process_{file}', bash_command=f'cat {file}') if prev_task: prev_task >> task prev_task = task -> Option DQuick Check:
Use prev_task >> task in loop for sequence = A [OK]
- Using same task_id for all tasks
- Setting dependencies in wrong order
- Not updating prev_task inside loop
Ready to prove it?
Pass the test and this pattern moves unknown
