Child Workflow related events logged to Event History
When using a Child Workflow API, Child Workflow related Events (StartChildWorkflowExecutionInitiated, ChildWorkflowExecutionStarted, ChildWorkflowExecutionCompleted, etc.) are logged in the Workflow Execution Event History.
.NET awaiting StartChildWorkflowAsync or ExecuteChildWorkflowAsync waits for ChildWorkflowExecutionStarted Event
In .NET, awaiting StartChildWorkflowAsync() or ExecuteChildWorkflowAsync() internally waits for the ChildWorkflowExecutionStarted Event before returning, so the Child Workflow is guaranteed to have started once the call resolves.
Child Workflow started from Signal or Update handler must resolve before Parent completion
If you start a Child Workflow from a non-main context (for example, a Signal or Update handler), make sure the Parent Workflow doesn't complete before that call resolves.
ExecuteChildWorkflowAsync starts Child Workflow and waits for completion
The ExecuteChildWorkflowAsync() method starts the Child Workflow and waits for completion.
StartChildWorkflowAsync starts Child Workflow and returns handle
The StartChildWorkflowAsync() method starts a Child Workflow and returns its handle. This is useful if you want to do something after it has only started, or to get the Workflow/Run ID, or to be able to signal it while running.
ExecuteChildWorkflowAsync is helper for StartChildWorkflowAsync plus await handle.GetResultAsync
ExecuteChildWorkflowAsync() is a helper method for StartChildWorkflowAsync() plus await handle.GetResultAsync().
Default Parent Close Policy is terminate
The default Parent Close Policy option is set to terminate the Child Workflow Execution.
Set ParentClosePolicy in ChildWorkflowOptions for .NET
Set the ParentClosePolicy property inside the ChildWorkflowOptions for ExecuteChildWorkflowAsync or StartChildWorkflowAsync to specify the behavior of the Child Workflow when the Parent Workflow closes.
.NET example: Execute Child Workflow with default settings
await Workflow.ExecuteChildWorkflowAsync((MyChildWorkflow wf) => wf.RunAsync());
.NET example: Execute Child Workflow with Abandon Parent Close Policy
await Workflow.ExecuteChildWorkflowAsync(
(MyChildWorkflow wf) => wf.RunAsync(),
new() { ParentClosePolicy = ParentClosePolicy.Abandon });
Parent Close Policy enum values in Go
The possible Parent Close Policy values in Go are:
- PARENT_CLOSE_POLICY_ABANDON
- PARENT_CLOSE_POLICY_TERMINATE
- PARENT_CLOSE_POLICY_REQUEST_CANCEL
Type: ParentClosePolicy
Default: PARENT_CLOSE_POLICY_TERMINATE
Parent Close Policy example in Go
Example of setting Parent Close Policy in Go:
import (
// ...
"go.temporal.io/api/enums/v1"
)
func YourWorkflowDefinition(ctx workflow.Context, params ParentParams) (ParentResp, error) {
// ...
childWorkflowOptions := workflow.ChildWorkflowOptions{
// ...
ParentClosePolicy: enums.PARENT_CLOSE_POLICY_ABANDON,
}
ctx = workflow.WithChildOptions(ctx, childWorkflowOptions)
childWorkflowFuture := workflow.ExecuteChildWorkflow(ctx, YourOtherWorkflowDefinition, ChildParams{})
// ...
}
func YourOtherWorkflowDefinition(ctx workflow.Context, params ChildParams) (ChildResp, error) {
// ...
return resp, nil
}
Child Workflow Execution definition in Go
A Child Workflow Execution is a Workflow Execution that is scheduled from within another Workflow using a Child Workflow API. When using a Child Workflow API, Child Workflow related Events (StartChildWorkflowExecutionInitiated, ChildWorkflowExecutionStarted, ChildWorkflowExecutionCompleted, etc.) are logged in the Workflow Execution Event History.
ChildWorkflowExecutionStarted Event requirement before parent completion
The ChildWorkflowExecutionStarted Event must be logged to the Event History before the Parent Workflow completes to ensure the Child Workflow has started. In Go, you must explicitly call GetChildWorkflowExecution() on the ChildWorkflowFuture and then call Get() on the returned Future to wait for this Event.
ExecuteChildWorkflow API in Go
To spawn a Child Workflow Execution in Go, use the ExecuteChildWorkflow API from the go.temporal.io/sdk/workflow package. The ExecuteChildWorkflow call requires an instance of workflow.Context with an instance of workflow.ChildWorkflowOptions applied to it, the Workflow Type, and any parameters to pass to the Child Workflow Execution. The ExecuteChildWorkflow call returns an instance of ChildWorkflowFuture.
ChildWorkflowOptions field inheritance in Go
workflow.ChildWorkflowOptions contain the same fields as client.StartWorkflowOptions. Workflow Option fields automatically inherit their values from the Parent Workflow Options if they are not explicitly set. If a custom WorkflowID is not set, one is generated when the Child Workflow Execution is spawned.
WithChildOptions API to apply Child Workflow Options
Use the WithChildOptions API to apply Child Workflow Options to an instance of workflow.Context. This is then passed to the ExecuteChildWorkflow call.
Waiting for Child Workflow results with ChildWorkflowFuture
Call the .Get() method on an instance of ChildWorkflowFuture to wait for the result of the Child Workflow Execution.
Async Child Workflows in Go - basic synchronous example
Example of synchronous child workflow execution in Go:
func YourWorkflowDefinition(ctx workflow.Context, params ParentParams) (ParentResp, error) {
childWorkflowOptions := workflow.ChildWorkflowOptions{}
ctx = workflow.WithChildOptions(ctx, childWorkflowOptions)
var result ChildResp
err := workflow.ExecuteChildWorkflow(ctx, YourOtherWorkflowDefinition, ChildParams{}).Get(ctx, &result)
if err != nil {
// ...
}
// ...
return resp, nil
}
func YourOtherWorkflowDefinition(ctx workflow.Context, params ChildParams) (ChildResp, error) {
// ...
return resp, nil
}
Async Child Workflows requirement - Abandon Parent Close Policy
To asynchronously spawn a Child Workflow Execution, the Child Workflow must have an 'Abandon' Parent Close Policy set in the Child Workflow Options. Additionally, the Parent Workflow Execution must wait for the ChildWorkflowExecutionStarted Event to appear in its Event History before it completes.
Async Child Workflows - Parent completion risk
If the Parent makes the ExecuteChildWorkflow call and then immediately completes, the Child Workflow Execution does not spawn.
Async Child Workflows - waiting for execution to start
To ensure that the Child Workflow Execution has started, first call the GetChildWorkflowExecution method on the instance of the ChildWorkflowFuture, which will return a different Future. Then call the Get() method on that Future, which will wait until the Child Workflow Execution has spawned.
Async Child Workflows in Go - with Abandon Parent Close Policy example
Example of asynchronous child workflow execution in Go with Abandon Parent Close Policy:
import (
// ...
"go.temporal.io/api/enums/v1"
)
func YourWorkflowDefinition(ctx workflow.Context, params ParentParams) (ParentResp, error) {
childWorkflowOptions := workflow.ChildWorkflowOptions{
ParentClosePolicy: enums.PARENT_CLOSE_POLICY_ABANDON,
}
ctx = workflow.WithChildOptions(ctx, childWorkflowOptions)
childWorkflowFuture := workflow.ExecuteChildWorkflow(ctx, YourOtherWorkflowDefinition, ChildParams{})
// Wait for the Child Workflow Execution to spawn
var childWE workflow.Execution
if err := childWorkflowFuture.GetChildWorkflowExecution().Get(ctx, &childWE); err != nil {
return err
}
// ...
return resp, nil
}
func YourOtherWorkflowDefinition(ctx workflow.Context, params ChildParams) (ChildResp, error) {
// ...
return resp, nil
}
Parent Close Policy definition in Go
A Parent Close Policy determines what happens to a Child Workflow Execution if its Parent changes to a Closed status (Completed, Failed, or Timed Out).
Parent Close Policy default value in Go
The default Parent Close Policy option is set to terminate the Child Workflow Execution.
Parent Close Policy configuration in Go
In Go, a Parent Close Policy is set on the ParentClosePolicy field of an instance of workflow.ChildWorkflowOptions. The possible values can be obtained from the go.temporal.io/api/enums/v1 package.
Child Workflow independent execution and history
Each child Workflow executes as an independent Workflow with its own Workflow ID, event history (50K event limit), and lifecycle. Child Workflows maintain separate histories, preventing parent history bloat.
Child Workflow key capabilities
Child Workflows provide: independent identity with unique Workflow ID visible in UI for tracking and querying, separate event history, flexible invocation via synchronous (blocking) or asynchronous (non-blocking) execution, lifecycle control through Parent Close Policy, Task Queue routing to specialized Workers, and reusability across multiple parent Workflows.
Child Workflow vs Activity distinction
Activities execute code (especially external operations like API calls or database queries), while Child Workflows orchestrate processes. Use Child Workflows when you need a separate Workflow ID, independent tracking, operations that may outlive the parent, reuse across multiple parents, execution on different Task Queues, independent history and event limits, or different timeouts/retry policies at Workflow level. Use Activities for short-lived external operations tightly coupled to parent lifecycle when lower overhead is important.
ParentClosePolicy values and behavior
ParentClosePolicy determines Child Workflow behavior when parent closes. TERMINATE: child is terminated when parent closes (for tightly coupled processes). ABANDON: child continues independently (for fire-and-forget, long-running tasks). REQUEST_CANCEL: child receives cancellation request (for graceful cleanup).
Synchronous vs asynchronous child execution in Python
In Python, workflow.execute_child_workflow() starts a child Workflow and awaits its completion (synchronous/blocking). workflow.start_child_workflow() starts a child Workflow asynchronously, returning a handle once the child has started without waiting for completion.
Synchronous vs asynchronous child execution in Go
In Go, workflow.ExecuteChildWorkflow() returns a ChildWorkflowFuture. Calling .Get() on the future blocks until the child completes (synchronous). To execute asynchronously, call ExecuteChildWorkflow in a loop without calling .Get() immediately; later call .Get() on each future to collect results. childFuture.GetChildWorkflowExecution().Get() blocks until the child has started (not when it completes).
Synchronous vs asynchronous child execution in Java
In Java, Workflow.newChildWorkflowStub() creates a typed stub and calling a method on it blocks the parent (synchronous). Async.function() starts a child Workflow asynchronously. Workflow.getWorkflowExecution(child) returns a Promise that resolves when the child starts (not when it completes).
Synchronous vs asynchronous child execution in TypeScript
In TypeScript, executeChild() starts a child Workflow and awaits its completion (synchronous/blocking). startChild() returns a handle once the child has started without waiting for completion (asynchronous).
Fire-and-forget pattern requirement
When using fire-and-forget pattern with asynchronous child execution, you must wait for the child to start before the parent completes. Without this, the parent could complete before the child is scheduled and the child would never execute. The ABANDON policy ensures the child continues running after the parent completes.
Parallel child execution pattern
To execute multiple Child Workflows in parallel, start all children first (which returns immediately), then wait for all of them to complete. In Python use asyncio.gather(), in TypeScript use Promise.all(), in Java use Promise.allOf().get(), in Go use a loop to call ExecuteChildWorkflow for each child, then loop again calling .Get() on each future.
Child Workflow failure handling
Child Workflow failures propagate to the parent as a Child Workflow Failure exception (ChildWorkflowFailure in TypeScript and Java, ChildWorkflowError in Python, ChildWorkflowExecutionError in Go), with the underlying cause in its cause field. If not caught and handled, the parent Workflow fails as well.
Child Workflow benefits
Child Workflows provide modularity by breaking complex logic into reusable units. Each child is a first-class Workflow with its own ID for tracking, its own 50K event history limit, and its own execution timeout configuration. Children can outlive parents with ABANDON policy. Multiple children can execute concurrently. Child failures do not automatically fail the parent. The same Child Workflow can be reused by multiple parents.
Child Workflow trade-offs
Each child is a separate Workflow execution with its own history (overhead). There are more moving parts than a single monolithic Workflow. Child execution details are not in the parent history but are queryable independently. Async children require explicit synchronization if needed. More Workflow executions mean higher resource usage. Starting a Child Workflow has more overhead than starting an Activity.
Common pitfall: treating Child Workflows like Activities
Child Workflows are for orchestration, not for executing external code. If you only need to call an API or run a function, use an Activity instead. Do not use Child Workflows as a substitute for Activities.
Common pitfall: spawning unbounded children
Starting thousands of Child Workflows without batching can overwhelm the Temporal Service and bloat the parent's event history. Use fixed-size batches or a sliding window pattern instead.
Common pitfall: ignoring Parent Close Policy default
The default ParentClosePolicy is TERMINATE, which kills children when the parent closes. If children must outlive the parent, you must set the policy to ABANDON explicitly.
Common pitfall: synchronous calls blocking parent
Calling a Child Workflow synchronously blocks the parent until the child completes. For long-running children, use the async API to avoid stalling the parent.
Common pitfall: omitting Workflow IDs
Without explicit Workflow IDs, you lose the ability to deduplicate or look up Child Workflows by a meaningful identifier. Generate deterministic IDs based on business keys.
Child Workflow best practice: unique Workflow IDs
Generate unique IDs for Child Workflows to avoid conflicts. Use deterministic IDs based on business keys for deduplication and lookup.
Child Workflow best practice: choose appropriate policy
Use TERMINATE for tightly coupled children and ABANDON for independent children. Set timeouts appropriately for child executions.
Child Workflow best practice: limit parallelism
Do not spawn unlimited children; use batch patterns for large datasets. Use fixed-size batches or sliding window patterns.
Child Workflow best practice: use typed stubs
Prefer typed stubs over untyped for compile-time safety. Monitor child executions by tracking Child Workflow IDs for observability and debugging.
Conditional child execution pattern
You can conditionally start different Child Workflows based on business logic. The parent checks conditions and only starts specific Child Workflows when needed, allowing selective execution paths.
Child Workflow vs alternatives comparison
Child Workflow: High modularity, independent history, can outlive parent (ABANDON), medium overhead, separate Workflow ID. Activity: Medium modularity, no independent history, cannot outlive parent, low overhead, no separate ID. Separate Workflow + Signals: High modularity, independent history, can outlive parent, high overhead, separate ID. Async Lambda: Low modularity, no independent history, cannot outlive parent, very low overhead, no separate ID.
Fan-Out pattern: split records into fixed-size chunks with one child Workflow per chunk
The Fan-Out pattern distributes a large record set across multiple independent child Workflows, each responsible for processing a fixed-size chunk. The parent Workflow assigns work by passing only an offset and length (two integers per child) rather than explicit record IDs. This keeps history events small and allows each chunk to be processed independently with automatic retries of failed children without re-processing records handled by other children.
Fan-Out pattern problem: single Workflow limits
A single Workflow run can have at most 2,000 in-flight Activities (aim for 500) and at most 50,000 history events. Processing millions of records in a single Workflow run is not possible, necessitating partitioning strategies like Fan-Out.
Fan-Out pattern: when to use and concurrency limits
Use Fan-Out when you want maximum concurrency with no rate control and you can pre-compute how many chunks you need before the job starts. Keep the number of in-flight children per parent well under the default limit of 2,000. For larger workloads, use Sliding Window or Batch Iterator patterns instead.
Fan-Out pattern implementation: parent and child Workflow structure
The parent Workflow receives the total record count and chunk size, divides the total into chunks, and starts one child Workflow per chunk passing only offset and length. Each child independently fetches its slice of records and executes an activity for each record (like processRecord). The parent blocks until all children complete, then returns aggregated results.
Fan-Out best practice: use offset and length instead of explicit record IDs
Pass only two integers (offset and length) to each child Workflow rather than a full slice of record IDs. The child fetches its own records. This approach keeps history events small and avoids storing millions of IDs in event history.
Fan-Out best practice: chunk size relative to Activity limit
Size chunks to stay under the Activity limit of 2,000 in-flight Activities per child Workflow. Aim for chunks of 500 records or fewer if each record maps to one Activity.
Fan-Out best practice: set deterministic Workflow IDs for children
Give each child Workflow a deterministic ID using the pattern parentId/batch-<offset>. This makes it safe to re-run the parent because Temporal deduplicates child starts by Workflow ID, ensuring already-completed children are not re-executed.
Fan-Out best practice: cap concurrent child starts
Starting thousands of child Workflows simultaneously puts pressure on the namespace. Consider batching child starts or using Sliding Window pattern if tighter concurrency control is needed.
Fan-Out best practice: PARENT_CLOSE_POLICY_ABANDON for fire-and-forget
Set PARENT_CLOSE_POLICY_ABANDON for fire-and-forget fan-outs where the parent does not need to collect results. With the default TERMINATE policy, cancelling or timing out the parent will terminate all in-flight children.
Fan-Out pitfall: starting too many children at once
Each child start adds to the parent's history. Temporal enforces a default limit of 2,000 pending (in-flight) child Workflows per parent. Keep well under this limit. If you need more children, switch to MapReduce Tree or Sliding Window patterns.