---
title: "Step"
description: "A Step is one unit of background work in a Flow. It"
canonical_url: "https://docs.superdurable.io/concepts/step/"
source: "docs/content/concepts/step/index.mdx"
locale: "en"
---

# Step

A **Step** is one unit of background work in a [Flow](https://docs.superdurable.io/concepts/flow.md). It
can wait durably for something to happen, then do its work, then choose what
runs next. Dex runs Steps on your Workers and records each result.

A native struct or class implements the Step. Its type is the **StepType**.
One Step type can run many times in a Flow, even in parallel. Each run is a
**StepExecution**, identified by a **StepExecutionID** such as
**Charge-2**.

<a id="step-input"></a>

## Step input

Each Step execution has its own typed input. Dex stores it with the execution.
The input tells executions of the same Step type apart. The start Step's input
is the Flow's start input.

<a id="waitfor-and-execute"></a>

## WaitFor and Execute

A Step has one or two methods.

- **WaitFor** is optional. It returns a durable **Wait**. Dex stores the wait,
  checks its conditions for you, and calls **Execute** once they are met.
- **Execute** is required. It does the work and returns a **StepDecision**.

If a Step has no **WaitFor**, **Execute** runs immediately.

The WaitFor of ExampleStep returns a Wait, and Dex evaluates its Timer, Channel, or SubFlow condition. When the condition is satisfied, Execute returns GoTo(Complete Flow, input + 1). A StepDecision can be GoTo, GracefulComplete, or ForceFail.

This Step waits for an approval message, then moves to a second Step that
completes the Flow.

**Python**

```python
approval = Channel("Approval", str)


class StepSecond(Step[int]):
    def execute(self, context: Context, input: int) -> StepDecision:
        return graceful_complete(input + 1)


class Example(Step[int]):
    def __init__(self, second: StepSecond) -> None:
        self.second = second

    def wait_for(self, context: Context, input: int) -> Wait:
        return Wait.until(approval.for_one())

    def execute(self, context: Context, input: int) -> StepDecision:
        return go_to(StepSecond, input + 1)
```

Example: [examples/python/dex_examples/primitives/step/step_flow.py](https://github.com/superdurable/dex/tree/main/examples/python/dex_examples/primitives/step/step_flow.py)

**Go**

```go
var Approval = dex.DefineChannel[string]("Approval")

type Example struct {
	dex.StepDefaults
}

func (Example) WaitFor(_ dex.Context, _ int) (*dex.Wait, error) {
	return dex.Until(Approval.ForOne()), nil
}

func (Example) Execute(_ dex.Context, input int) (*dex.StepDecision, error) {
	return dex.GoTo(stepSecond{}, input+1), nil
}

type stepSecond struct {
	dex.StepDefaultsNoWaitFor[int]
}

func (stepSecond) Execute(_ dex.Context, input int) (*dex.StepDecision, error) {
	return dex.GracefulComplete(input + 1), nil
}
```

Example: [examples/go/primitives/step/workflow.go](https://github.com/superdurable/dex/tree/main/examples/go/primitives/step/workflow.go)

**Java**

```java
final class Example implements Step<Integer> {
    @Override
    public Class<Integer> getInputType() {
        return Integer.class;
    }

    @Override
    public Wait waitFor(final Context context, final Integer input) {
        return Wait.until(approval.forOne());
    }

    @Override
    public StepDecision execute(final Context context, final Integer input) {
        return StepDecision.goTo(StepSecond.class, input + 1);
    }
}

static final class StepSecond implements Step<Integer> {
    @Override
    public Class<Integer> getInputType() {
        return Integer.class;
    }

    @Override
    public StepDecision execute(final Context context, final Integer input) {
        return StepDecision.gracefulComplete(input + 1);
    }
}
```

Example: [examples/java/src/main/java/io/superdurable/dex/primitives/step/StepFlow.java](https://github.com/superdurable/dex/tree/main/examples/java/src/main/java/io/superdurable/dex/primitives/step/StepFlow.java)

**TypeScript**

```typescript
const approval = new Channel("Approval", stringCodec);

class Example implements Step<number> {
  public readonly inputCodec = doubleCodec;

  public constructor(private readonly flow: StepFlow) {}

  public getStepType(): string {
    return "Example";
  }

  public waitFor(_context: Context, _input: number): Wait {
    return Wait.until(approval.forOne());
  }

  public execute(_context: Context, input: number): StepDecision {
    return goTo(StepSecond, input + 1);
  }
}

class StepSecond implements Step<number> {
  public readonly inputCodec = doubleCodec;

  public getStepType(): string {
    return "StepSecond";
  }

  public execute(_context: Context, input: number): StepDecision {
    return gracefulComplete(input + 1);
  }
}
```

Example: [examples/typescript/src/primitives/step/step-flow.ts](https://github.com/superdurable/dex/tree/main/examples/typescript/src/primitives/step/step-flow.ts)

**Rust**

```rust
static APPROVAL: LazyLock<Channel<String>> = LazyLock::new(|| Channel::new("Approval"));

#[derive(Default)]
struct Example;

impl Step for Example {
    type Input = i32;

    fn wait_for(&self, _context: &mut Context, _input: Self::Input) -> HandlerResult<Wait> {
        Ok(Wait::until(APPROVAL.for_one()))
    }

    fn execute(&self, _context: &mut Context, input: Self::Input) -> HandlerResult<StepDecision> {
        Ok(StepDecision::go_to(&StepSecond, input + 1))
    }
}

#[derive(Default)]
struct StepSecond;

impl Step for StepSecond {
    type Input = i32;

    fn execute(&self, _context: &mut Context, input: Self::Input) -> HandlerResult<StepDecision> {
        Ok(StepDecision::graceful_complete(input + 1))
    }
}
```

Example: [examples/rust/src/primitives/step/flow.rs](https://github.com/superdurable/dex/tree/main/examples/rust/src/primitives/step/flow.rs)

<a id="durable-waitfor"></a>

<a id="waiting"></a>

## Waiting

A wait does not hold a Worker or a thread. Dex keeps it in the Flow's state
and wakes the Step when a condition is met. A Step can wait on three kinds of
condition:

- a [Timer](https://docs.superdurable.io/concepts/primitives/timer.md) fires;
- a message arrives on a [Channel](https://docs.superdurable.io/concepts/primitives/channel.md);
- a [SubFlow](https://docs.superdurable.io/concepts/primitives/subflow.md) completes.

Combine conditions with **AnyOf** or **AllOf**. **AnyCombinationOf** accepts
any one of several groups of conditions. After the wait, **Execute** reads what
satisfied it, such as the Channel messages it consumed. See
[Waits](https://docs.superdurable.io/reference/python/waits.md).

To wait for parallel branches, use a **Join**. It is not another kind of
condition: it waits on a ChannelMap that the branches report to. See
[Await Parallel Steps](https://docs.superdurable.io/design-patterns/parallel/await.md).

<a id="stepdecision-movements"></a>

## StepDecision

**Execute** returns a **StepDecision**. It can go to one next Step or to
several Steps at once. This is how you build branching, loops, and parallel
work.

GoTo: ExampleStep returns GoTo(Complete Flow), which completes the Flow. GoToMany: ExampleStep starts StepA and StepB, which run in parallel. Loop: ExampleStep goes to ProcessStep, and ProcessStep goes back to ExampleStep.

A decision can also close the Flow or end a branch:

- **GracefulComplete** asks Dex to complete the Flow once no other Step is active.
- **ForceComplete** completes the Flow immediately.
- **ForceFail** fails the Flow immediately.
- **DeadEnd** ends this branch without closing the Flow.

Completing decisions can return the Flow output, which a client reads as the
Flow result. When several completing decisions return an output, the result
keeps each one. Only a Flow that completes keeps these outputs. If the Flow does
not complete because it fails, is canceled, is terminated, or times out, its
result has no outputs.

A decision can also cancel other active Steps. See the
[Parallel Steps](https://docs.superdurable.io/design-patterns/parallel.md) and
[Parallel SubFlows](https://docs.superdurable.io/design-patterns/parallel-subflows.md) patterns.

<a id="stepoptions"></a>

## StepOptions

A Step returns **StepOptions** to control how its methods run. **WaitFor** and
**Execute** each have their own settings.

- **Timeout** limits one attempt of a method.
- **Retry policy** runs the same method again after a failed attempt.
- <a id="waitfor-failure-policy"></a><a id="execute-failure-policy"></a><a id="step-failed-events-and-last-failure-info"></a>**Failure
  policy** decides what happens when retries run out. By default the Flow
  fails. **Execute** can go to a recovery Step instead. **WaitFor** can proceed
  to **Execute**. See [WaitFor failure policy](https://docs.superdurable.io/reference/python/failure-and-retries.md#waitfor-failure-policy),
  [Execute failure policy](https://docs.superdurable.io/reference/python/failure-and-retries.md#execute-failure-policy), and
  [Last failure info](https://docs.superdurable.io/reference/python/failure-and-retries.md#last-failure-info).
- **Durability** chooses when Dex stores the result. **SYNC**, the default,
  stores it before the next work starts. **ASYNC** is faster, but a server
  failure can make the method run again.
- **Heartbeat** lets a long method report progress and save a checkpoint.
- **State loading** and **Attribute locks** choose which state the method
  loads and which Attributes it locks.

A **StepDecision** can override the options for one Step execution.

<a id="guarantees"></a>

## Guarantees

Dex saves each **WaitFor** or **Execute** call on its own. It saves the call's
Attribute writes and Channel publications together with its result, and only
when the method succeeds. Calls to external systems are not part of this save.

A Step method's changes take effect all at once. They are not protected from
other Steps and RPCs, though. A method reads the Flow state as it was when Dex
handed the method to a Worker. Other Steps and RPCs can change that state
before the method's changes take effect. Dex does not check that the values
the method read are still current. So two Steps or RPCs that read and then
write the same Attribute can overwrite each other's update. To prevent this,
have both declare a lock on that Attribute. Dex then runs them one at a time.
See [Attribute locks](https://docs.superdurable.io/concepts/primitives/attribute.md#locking).

<a id="failure-and-redelivery"></a>

<a id="heartbeats-not-acknowledged"></a>

<a id="same-attempt-redelivery"></a>

<a id="unknown-outcome"></a>

<a id="design-for-redelivery"></a>

A Step method can run more than once. A retry runs it again after a failure.
Dex cannot always tell whether a failed attempt ran the work. Make each
external effect idempotent. Key it by the Flow ID and the StepExecutionID, or
by your own business key. See
[Failure and redelivery](https://docs.superdurable.io/reference/python/failure-and-retries.md#failure-and-redelivery) for the cases where
Dex cannot tell.

When retries run out, the recovery Step learns whether the work certainly did
not run, or may have run.

Canceling a Step discards everything its current method has not saved: its
Attribute writes, its Channel publications, and its decision. What its
**WaitFor** already saved stays. A decision or an RPC never cancels the Steps that it
starts itself. [Channel guarantees](https://docs.superdurable.io/concepts/primitives/channel.md#guarantees)
say what happens to the messages a canceled Step consumed.

Dex does not stop a canceled method's code. The method should check for
cancellation and return. Until it does, its code keeps running. Its calls to external
systems still take effect, even after the Flow has closed.

**GracefulComplete** ends its own branch and asks Dex to complete the Flow. Dex
begins to complete the Flow as soon as no Step is active. Until then, other
Steps and RPCs can still start new Steps, and those Steps delay completion too.
A later branch that ends with **DeadEnd** does not withdraw the request. After
the last Step ends, the Flow can stay **RUNNING** briefly while Dex finishes
pending Attribute Sync writes and transactional RPCs that already started.

Without a pending **GracefulComplete**, a Flow with no active Step keeps
running. An RPC can still start new Steps in it. End the last branch with a
completing decision. If a Flow must not wait forever, set a Flow timeout.

Dex starts ready Steps roughly in the order they became ready, but not
strictly. Do not rely on the order in which parallel Steps start or finish.
Use a Channel or an Attribute lock when the order matters.

## Reference

The SDK reference covers:

- [Step methods](https://docs.superdurable.io/reference/python/step.md) and what each one commits;
- [waits](https://docs.superdurable.io/reference/python/waits.md), condition results, and
  [AnyCombinationOf](https://docs.superdurable.io/reference/python/waits.md#anycombinationof);
- [Step decisions](https://docs.superdurable.io/reference/python/step-decisions.md), including
  [ForceCompleteIfChannelsEmpty](https://docs.superdurable.io/reference/python/step-decisions.md#forcecompleteifchannelsempty);
- [retry and failure policies](https://docs.superdurable.io/reference/python/failure-and-retries.md#retry-and-failure-policies),
  [durability](https://docs.superdurable.io/reference/python/step-options.md#durability),
  [timeouts](https://docs.superdurable.io/reference/python/step-options.md#timeouts-and-total-duration),
  [heartbeats](https://docs.superdurable.io/reference/python/step-options.md#heartbeat), and
  [cancellation](https://docs.superdurable.io/reference/python/step-options.md#cancellation);
- [state loading](https://docs.superdurable.io/reference/python/step-options.md#selective-state-loading) and
  [Attribute locks](https://docs.superdurable.io/reference/server/runtime-data.md#attribute-locks);
- [failure and redelivery](https://docs.superdurable.io/reference/python/failure-and-retries.md#failure-and-redelivery).
