Workflows Guru

Dynamic Workflows: How to Build Workflows That Adapt at Runtime

Published

Most workflows are designed around a sequence of tasks. Data is extracted, transformed, validated, and loaded. An application is deployed, tested, and released. A virtual machine is provisioned, configured, and registered.

But what happens when you don't know all the steps before execution begins?

Imagine processing documents from hundreds of customers. One customer might have three documents, another might have thousands. Some documents require OCR, others need language detection, and a few require manual validation. Defining a separate workflow for every possible combination would quickly become difficult to maintain.

This is where dynamic workflows become useful.

In this article, we'll explore what dynamic workflows are, when to use them, practical implementation examples, and how popular workflow orchestration engines support them.

What Is a Dynamic Workflow?

A dynamic workflow is a workflow whose execution structure can change based on information available at runtime. It adapts its execution plan according to runtime information, such as task outputs, incoming data, business rules, or external events. Instead of executing exactly the same predefined sequence every time, it can create additional tasks, select different execution paths, or launch child workflows as needed.

In a conventional workflow, such as a sequential workflow or a static DAG, the tasks and their dependencies are usually defined before execution. The workflow engine follows that structure, even if some tasks are skipped or receive different inputs.

A dynamic workflow goes further: information obtained during execution can determine which tasks are created, how many times a task runs, or which workflow should execute next.

Consider a simple data processing pipeline:

  1. Discover files that need processing.
  2. Process each file.
  3. Validate the results.
  4. Generate a report.

If the system processes five files today and five hundred tomorrow, the number of processing tasks can change accordingly. The orchestration engine doesn't need a separate workflow definition for each file count.

Static versus Dynamic Workflows

Consider a workflow that processes customer data. A static implementation might define three processing tasks in advance:

1extract_customer_a()
2extract_customer_b()
3extract_customer_c()

This approach works when the number of customers is known and relatively stable. Adding another customer requires modifying the workflow definition.

A dynamic implementation instead discovers the customers at runtime:

1customers = discover_customers()
2for customer in customers:
3 process_customer(customer)

The number of processing operations now depends on the result of discover_customers().

In a real orchestrated workflow, the engine must also manage task execution, dependencies, failures, retries, and observability. Simply writing a Python loop does not automatically provide those orchestration capabilities.

The important distinction is not whether the workflow contains a loop. It is whether the work to be orchestrated depends on information that becomes available during execution.

Examples of Dynamic Workflows

Dynamic workflows can adapt in several different ways. These patterns are related, but they are not interchangeable.

Dynamic Task Mapping

Dynamic task mapping creates multiple task instances from a collection of inputs.

For example, a data ingestion pipeline discovers files in cloud storage and launches one processing task per file. If the discovery task returns 200 files, the engine can create 200 task instances. If it returns 20 files on the next run, it can create only 20.

The workflow definition describes the processing logic, while the runtime data determines how many times that logic executes. This is especially useful for:

  • Processing files, documents, images, or messages.
  • Running tests against a variable number of services.
  • Processing customer accounts independently.
  • Executing batch jobs over partitions.
  • Evaluating multiple machine learning models.

Conditional Execution

A dynamic workflow can choose its execution path based on a condition evaluated at runtime. For example, a deployment pipeline might run different validation steps depending on the type of application being deployed.

1if application_type == "frontend":
2 run_frontend_tests()
3elif application_type == "backend":
4 run_api_tests()
5else:
6 run_generic_tests()

The possible branches may be defined in advance, but the workflow selects which branch to execute using runtime information. This is a form of dynamic control flow, even when no new task types are created.

Runtime-Generated Dependencies

Sometimes, it is not enough to determine which tasks should run. The workflow must also determine their dependencies.

Imagine a deployment involving several services. Service B depends on service A, while service C depends on both A and B. Another service may be completely independent. The workflow must establish an execution order based on dependency information discovered at runtime.

This pattern is useful for:

  • Deploying microservices.
  • Building software from dependency graphs.
  • Provisioning infrastructure resources.
  • Executing data pipelines assembled from metadata.
  • Processing tasks with dynamically discovered prerequisites.

Not every engine that supports dynamic task mapping can freely construct an arbitrary dependency graph at runtime. Some engines let you repeat predefined task templates, while others offer more flexible programmatic control flow.

Dynamic Child Workflows

A workflow can also start other workflows at runtime. For example, an order-processing workflow might launch a payment workflow, an inventory workflow, and a shipping workflow depending on the order's contents.

Each child workflow can have its own lifecycle, retries, monitoring, and execution history. This is useful when individual processes are substantial enough to deserve independent orchestration rather than being represented as ordinary tasks in one large workflow.

Dynamic Workflows Use Cases

Dynamic workflows are most valuable when the work varies in size, structure, or execution path.

Variable workloads. The number of items to process changes between runs. Examples include handling a variable number of incoming events or processing all files found in a storage bucket.

Data-dependent execution. The next steps depend on results from previous tasks. For example, a data quality check might determine whether a dataset requires additional cleaning, manual review, or immediate publication.

Infrastructure provisioning. Infrastructure automation often requires a variable number of resources. A provisioning workflow might discover which virtual machines, networks, storage volumes, or security rules are required for a particular application. It then orchestrates the corresponding operations while respecting dependencies and security constraints.

Event-driven business processes. Some business processes cannot be fully described by a single fixed sequence. An insurance claim, for example, might require additional document collection, fraud checks, or human approval depending on the information received. Dynamic control flow lets the orchestration logic adapt to those requirements.

AI and data processing pipelines. AI pipelines frequently need to process a variable number of documents, invoke tools conditionally, or route requests to different processing strategies. For example, a document-processing system might perform OCR only when a PDF contains scanned pages, extract tables only when tables are detected, and invoke a human reviewer when confidence falls below a threshold.

However, dynamic orchestration does not require AI. It is a general workflow design pattern that can be applied to conventional data engineering, infrastructure automation, and business processes. If you are interested in workflows where an LLM decides the next step, see our guide to agentic workflows.

Dynamic Workflows in Different Orchestration Engines

Workflow orchestration engines implement dynamic execution in different ways. Some use explicit mapping primitives, some allow ordinary programming-language control flow, and others execute reusable workflow templates. The right choice depends on how much of the workflow needs to be dynamic.

Apache Airflow: Dynamic Task Mapping

Apache Airflow provides Dynamic Task Mapping, which allows the scheduler to create task instances at runtime based on the output of an upstream task. A task can produce a list of inputs, and a downstream task can use .expand() to execute once for each item.

1from airflow.sdk import task
2
3@task
4def discover_files():
5 return ["file-a.csv", "file-b.csv", "file-c.csv"]
6
7@task
8def process_file(filename):
9 print(f"Processing {filename}")
10
11@task
12def summarize(results):
13 print(results)
14
15files = discover_files()
16results = process_file.expand(filename=files)
17summarize(results)

This illustrates the core mapping pattern in the Airflow 3.x Task SDK. The surrounding DAG and imports should follow the version of Airflow you deploy. Airflow also supports collecting mapped results, combining mapped and fixed arguments, and other mapping patterns. New to Airflow? Start with our Airflow tutorial.

What it supports: Runtime fan-out, mapping over upstream task outputs, and collecting results from mapped tasks.

What it does not provide in the same way as a fully programmatic engine: Arbitrary workflow construction using unrestricted Python control flow during task execution. Airflow still uses a DAG-oriented authoring model, and runtime mapping operates within that model. This distinction matters when your workflow needs to generate an entirely new dependency graph rather than repeat known tasks over a variable number of inputs.

Official documentation: Airflow Dynamic Task Mapping.

Prefect: Python-Native Dynamic Execution

Prefect takes a more Python-oriented approach. Flows can use ordinary conditionals, loops, functions, and nested flows to express runtime decisions.

1from prefect import flow, task
2
3@task
4def discover_services():
5 return ["api", "worker", "scheduler"]
6
7@task
8def deploy_service(service):
9 print(f"Deploying {service}")
10
11@flow
12def deployment_pipeline():
13 services = discover_services()
14 for service in services:
15 deploy_service(service)
16
17if __name__ == "__main__":
18 deployment_pipeline()

This example creates task runs as the loop executes. For parallel execution, Prefect also provides task submission and mapping patterns, depending on the task runner and execution requirements. The advantage is that developers can express dynamic behavior using familiar Python constructs rather than describing every possible branch in a static DAG. See our Prefect tutorial for a hands-on introduction.

What it supports: Runtime task creation, conditional branches, loops, nested flows, and dynamic fan-out.

What to consider: Python's flexibility does not mean every dynamically generated graph is automatically parallel. Execution ordering, task runners, concurrency, and result dependencies still matter.

Official documentation: Prefect flows and tasks.

Dagster: Dynamic Outputs, Mapping, and Collect

Dagster supports dynamic execution through DynamicOut and DynamicOutput. An operation can emit an unknown number of outputs. Downstream operations can then be mapped over those outputs or collect them into a list.

1import dagster as dg
2
3@dg.op(out=dg.DynamicOut(str))
4def discover_files():
5 for filename in ["file-a.csv", "file-b.csv", "file-c.csv"]:
6 yield dg.DynamicOutput(
7 filename,
8 mapping_key=filename.replace(".", "_"),
9 )
10
11@dg.op
12def process_file(filename):
13 return f"processed:{filename}"
14
15@dg.op
16def summarize(results):
17 print(results)
18
19@dg.job
20def document_pipeline():
21 results = discover_files().map(process_file)
22 summarize(results.collect())

The example discovers filenames, emits a dynamic output for each one, maps a processing operation over those outputs, and collects the results. In Dagster, each mapping_key must uniquely identify an output within its dynamic output set.

What it supports: Runtime-generated outputs, mapped downstream operations, and result collection.

What to consider: Dynamic mapping is designed around the graph's declared operations. It is not the same as freely generating arbitrary new operation definitions and dependency structures at runtime. Dagster is particularly worth considering when data assets, lineage, and data-pipeline observability are central to the system.

Official documentation: Dagster Dynamic Mapping and Collect.

Temporal: Dynamic Control Flow and Child Workflows

Temporal is designed for durable execution of application workflows. Its workflow code can express conditional logic, loops, and decisions that determine which activities or child workflows run. Child workflows can be started as part of a parent workflow and tracked as separate workflow executions.

A simplified Python example illustrates the pattern:

1from temporalio import workflow
2
3@workflow.defn
4class DocumentWorkflow:
5 @workflow.run
6 async def run(self, document_ids: list[str]) -> list[str]:
7 results = []
8 for document_id in document_ids:
9 result = await workflow.execute_activity(
10 process_document,
11 document_id,
12 start_to_close_timeout=...,
13 )
14 results.append(result)
15 return results

This is illustrative rather than a complete runnable example: the activity definition, imports, timeout value, and worker setup must be supplied. The workflow can determine which activities to execute using runtime data. For larger independent processes, it can start child workflows instead.

What it supports: Runtime control flow, activity execution, and dynamically chosen child workflow executions.

What to consider: Temporal workflow code must obey deterministic execution constraints. External I/O and nondeterministic operations belong in activities or other appropriate mechanisms, rather than being performed arbitrarily inside workflow code. Temporal is a strong candidate for durable business processes, long-running operations, and workflows that need to survive failures while preserving execution state.

Official documentation: Temporal Child Workflows.

Argo Workflows: Dynamic Loops and Template Reuse

Argo Workflows provides runtime iteration through withItems, withSequence, and withParam. The particularly useful option is withParam, which accepts a JSON array generated by an earlier step. A later step can then execute a reusable template for each element. Conceptually:

1- name: process-files
2 template: process-file
3 withParam: "{{steps.discover.outputs.result}}"

Here, discover is expected to produce a JSON array, and process-file is a previously defined template. Our Argo Workflows tutorial covers templates and steps in more detail.

What it supports: Runtime iteration, fan-out over generated inputs, and reuse of predefined workflow templates.

What to consider: Argo's dynamic behavior is expressed through its workflow and template model. It is not equivalent to arbitrary Python code that constructs an unrestricted new workflow graph. Argo is especially relevant when your workloads already run on Kubernetes and you want orchestration built around containerized tasks.

Official documentation: Argo Workflows Loops.

What About Engines with Limited Dynamic Support?

It is more accurate to say that some engines have limited dynamic orchestration capabilities than to classify every engine as simply supporting or not supporting dynamic workflows.

For example, an engine might allow conditional execution while requiring all possible task types and dependencies to be declared ahead of time. Another might support runtime fan-out but not arbitrary runtime graph construction.

If you need runtime task generation, check whether the engine can create task instances from upstream outputs. If you need to construct new dependencies or launch independent workflow executions, verify those capabilities separately.

Also distinguish between a workflow engine and a system that merely executes a predefined script or job. A script can contain loops and conditional statements without the surrounding system tracking each operation as an individual task with orchestration-level retries, dependencies, and observability.

How to Choose the Right Approach

Before choosing an orchestration engine, identify what actually needs to change at runtime.

  • Choose dynamic task mapping when the overall workflow stays mostly the same but the number of items to process varies. Airflow and Dagster provide explicit mapping mechanisms, while Prefect also offers mapping and runtime task creation.
  • Choose Python-native control flow when runtime conditions determine which operations execute, and you want to express the logic naturally in application code. Prefect is a natural option for this style.
  • Choose child workflows when individual processes need independent execution histories, lifecycle management, or separate orchestration boundaries. Temporal provides first-class child workflows.
  • Choose template-based fan-out when your workload is built around reusable Kubernetes tasks and runtime-generated collections. Argo Workflows provides this model.

Finally, consider observability, concurrency limits, retry semantics, idempotency, and the operational complexity of the resulting workflow. A dynamic workflow may create hundreds or thousands of task instances, so the ability to control and inspect execution becomes just as important as the ability to create tasks dynamically. For a broader comparison of engines, see our best workflow orchestration tools page.

Conclusion

Dynamic workflows are useful when the work to execute cannot be fully determined before runtime. They let orchestration systems adapt to variable inputs, runtime conditions, and dependencies discovered during execution.

However, dynamic orchestration is not a single feature. Dynamic task mapping, conditional execution, runtime dependency generation, and child workflows solve different problems.

Airflow and Dagster provide explicit mapping abstractions. Prefect offers Python-native dynamic execution. Temporal supports durable, programmatic workflow control flow and child workflows. Argo Workflows provides dynamic iteration through reusable templates.

The best engine is the one whose execution model matches the kind of variability your application needs. Start by identifying whether you need a variable number of tasks, different execution branches, or entirely separate workflows, and evaluate orchestration tools against that requirement.