Skip to content

Wait Operations

Time-based durable waits

The Wait operation of the DurableContext pauses execution for a specified time without consuming compute. The SDK will checkpoint the start of the wait operation, the function suspends and Lambda exits, and then the backend automatically resumes execution when the wait completes. The SDK will replay and resume processing from where it had paused for the wait.

Unlike language-native sleep functions such as setTimeout(), time.sleep(), or Thread.sleep(), durable wait operations do not consume Lambda execution time. The durable function invocation exits cleanly after it checkpoints the start of the wait and resumes later at the specified time, even if the wait lasts hours or days.

The minimum wait duration is 1 second. The maximum wait duration is the maximum execution duration of 1 year. There is no cost associated with longer waits.

You cannot cancel a wait after it has started.

When to use wait

Use context.wait() for a time-based delay. For example, use waits between steps to delay the new next step in multi-step workflows.

Wait for an event or status change

To wait for an event or status change, rather than just a fixed delay, consider these alternatives:

  • Polling until a condition is met
  • Waiting for an external system response
    • Callbacks suspend your durable function until an external system sends a response.

Wait walkthrough

Here's a simple example of using a wait operation:

import {
  DurableContext,
  withDurableExecution,
} from "@aws/durable-execution-sdk-js";

export const handler = withDurableExecution(
  async (event: any, context: DurableContext) => {
    // Wait for 5 seconds
    await context.wait({ seconds: 5 });
    return "Wait completed";
  },
);
from aws_durable_execution_sdk_python import DurableContext, durable_execution
from aws_durable_execution_sdk_python.config import Duration

@durable_execution
def handler(event: dict, context: DurableContext) -> str:
    """Simple durable function with a wait."""
    # Wait for 5 seconds
    context.wait(duration=Duration.from_seconds(5))
    return "Wait completed"
import java.time.Duration;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableHandler;

public class BasicWaitExample extends DurableHandler<Object, String> {
    @Override
    public String handleRequest(Object input, DurableContext context) {
        // Wait for 5 seconds
        context.wait(null, Duration.ofSeconds(5));
        return "Wait completed";
    }
}

When this function runs:

  1. The SDK checkpoints the wait operation with a scheduled end time
  2. The Lambda function suspends
  3. After 5 seconds, the backend automatically invokes your function again
  4. Execution resumes after the wait and returns "Wait completed"

Method signature

wait(duration: Duration): DurablePromise<void>;
wait(name: string, duration: Duration): DurablePromise<void>;
def wait(
    self,
    duration: Duration,
    name: str | None = None,
) -> None
Void wait(String name, Duration duration);
DurableFuture<Void> waitAsync(String name, Duration duration);

Set name to null to omit it.

Parameters:

  • duration (required) - How long to wait. Must be at least 1 second. See Duration for how to specify durations in each programming language.
  • name (optional) - Only used for display, debugging and testing.

Returns:

DurablePromise<void>

None

Void (sync)

DurableFuture<Void> (async)

Raises/Throws:

None

ValidationError(DurableExecutionsError)

IllegalArgumentException

Duration

Duration signature

type Duration =
  | { days: number; hours?: number; minutes?: number; seconds?: number }
  | { hours: number; minutes?: number; seconds?: number }
  | { minutes: number; seconds?: number }
  | { seconds: number };
# module: aws_durable_execution_sdk_python.config


@dataclass
class Duration:
    seconds: int = 0

    @classmethod
    def from_seconds(cls, value: float) -> Duration: ...

    @classmethod
    def from_minutes(cls, value: float) -> Duration: ...

    @classmethod
    def from_hours(cls, value: float) -> Duration: ...

    @classmethod
    def from_days(cls, value: float) -> Duration: ...
// java.time.Duration (standard library)
Duration.ofSeconds(long seconds);
Duration.ofMinutes(long minutes);
Duration.ofHours(long hours);
Duration.ofDays(long days);

Duration usage

// Wait for 30 seconds
await context.wait({ seconds: 30 });

// Wait for 5 minutes
await context.wait({ minutes: 5 });

// Wait for 2 hours
await context.wait({ hours: 2 });

// Wait for 1 day
await context.wait({ days: 1 });

// Combined - 1 hour and 30 minutes
await context.wait({ hours: 1, minutes: 30 });
from aws_durable_execution_sdk_python.config import Duration

# Wait for 30 seconds
context.wait(duration=Duration.from_seconds(30))

# Wait for 5 minutes
context.wait(duration=Duration.from_minutes(5))

# Wait for 2 hours
context.wait(duration=Duration.from_hours(2))

# Wait for 1 day
context.wait(duration=Duration.from_days(1))
import java.time.Duration;

// Wait for 30 seconds
context.wait("wait30", Duration.ofSeconds(30));

// Wait for 5 minutes
context.wait("wait5m", Duration.ofMinutes(5));

// Wait for 2 hours
context.wait("wait2h", Duration.ofHours(2));

// Wait for 1 day
context.wait("wait1d", Duration.ofDays(1));

Named wait operations

Name wait operations to make them easier to identify in logs and tests.

import {
  DurableContext,
  withDurableExecution,
} from "@aws/durable-execution-sdk-js";

export const handler = withDurableExecution(
  async (event: any, context: DurableContext) => {
    // Wait with explicit name
    await context.wait("custom_wait", { seconds: 2 });
    return "Wait with name completed";
  },
);
from aws_durable_execution_sdk_python import DurableContext, durable_execution
from aws_durable_execution_sdk_python.config import Duration

@durable_execution
def handler(event: dict, context: DurableContext) -> str:
    """Durable function with a named wait."""
    # Wait with explicit name
    context.wait(duration=Duration.from_seconds(2), name="custom_wait")
    return "Wait with name completed"
import java.time.Duration;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableHandler;

public class NamedWaitExample extends DurableHandler<Object, String> {
    @Override
    public String handleRequest(Object input, DurableContext context) {
        // Wait with explicit name
        context.wait("custom_wait", Duration.ofSeconds(2));
        return "Wait with name completed";
    }
}

Scheduled end timestamp

Each wait operation has a scheduled end timestamp that indicates when it completes. This timestamp uses Unix milliseconds.

The ScheduledEndTimestamp field is in the checkpoint's WaitDetails. The SDK calculates the scheduled end time when it first checkpoints the wait operation:

{current time} + {wait duration} = {scheduled end timestamp}

Wait durations are approximate. The actual resume time depends on system scheduling, Lambda cold start time, and current system load.

Concurrency

Waits execute sequentially in the order they appear in your code. You can use waits inside parallel or map operations. If a branch or iteration of a parent is ready to suspend due to a wait, the durable function will wait for all child operations of that parent to complete or suspend before terminating the invocation.

In TypeScript and Java, you can run a wait concurrently with other operations. This is useful for enforcing a minimum duration — for example, ensuring at least 5 seconds pass while a step runs in parallel.

Don't await the wait immediately — use Promise.all to run it alongside other operations.

export const handler = withDurableExecution(
  async (event: any, context: DurableContext) => {
    // Start wait and step but don't await yet
    const waitPromise = context.wait("min-delay", { seconds: 5 });
    const stepPromise = context.step("process", async () => processData(event));

    // Wait for both — guarantees at least 5 seconds elapsed
    const [, result] = await Promise.all([waitPromise, stepPromise]);

    return result;
  },
);

Python waits are synchronous only. Use parallel or map for concurrency.

Use waitAsync() which returns a DurableFuture<Void>, then call .get() when you need to block.

import java.time.Duration;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableFuture;
import software.amazon.lambda.durable.DurableHandler;

public class AsyncWaitExample extends DurableHandler<Object, String> {
    @Override
    public String handleRequest(Object input, DurableContext context) {
        // Start wait and step concurrently — returns immediately
        DurableFuture<Void> waitFuture = context.waitAsync("min-delay", Duration.ofSeconds(5));
        DurableFuture<String> stepFuture = context.stepAsync("process", String.class, stepCtx -> processData(input));

        // Block until both complete — guarantees at least 5 seconds elapsed
        waitFuture.get();
        var result = stepFuture.get();

        return result;
    }
}

Testing

You can verify wait operations in your tests by inspecting the operations list:

import {
  LocalDurableTestRunner,
  OperationType,
  OperationStatus,
} from "@aws/durable-execution-sdk-js-testing";
import { handler } from "./multiple-waits";

let runner: LocalDurableTestRunner;

beforeAll(async () => {
  await LocalDurableTestRunner.setupTestEnvironment({ skipTime: true });
});

afterAll(async () => {
  await LocalDurableTestRunner.teardownTestEnvironment();
});

beforeEach(() => {
  runner = new LocalDurableTestRunner({ handlerFunction: handler });
});

it("should handle multiple sequential wait operations", async () => {
  const firstWait = runner.getOperation("wait-1");
  const secondWait = runner.getOperation("wait-2");

  const result = await runner.run();

  expect(result.getResult()).toEqual({ completedWaits: 2, finalStep: "done" });

  const operations = result.getOperations();
  expect(operations.length).toEqual(2);

  expect(firstWait.getType()).toBe(OperationType.WAIT);
  expect(firstWait.getStatus()).toBe(OperationStatus.SUCCEEDED);
  expect(firstWait.getWaitDetails()?.waitSeconds).toBe(5);

  expect(secondWait.getType()).toBe(OperationType.WAIT);
  expect(secondWait.getStatus()).toBe(OperationStatus.SUCCEEDED);
  expect(secondWait.getWaitDetails()?.waitSeconds).toBe(5);
});
@pytest.mark.durable_execution(handler=multiple_wait.handler)
def test_multiple_waits(durable_runner):
    """Test multiple sequential waits."""
    with durable_runner:
        result = durable_runner.run(input="test", timeout=20)

    assert result.status is InvocationStatus.SUCCEEDED

    # Find wait operations by name
    wait_1 = result.get_wait("wait-1")
    wait_2 = result.get_wait("wait-2")

    assert wait_1.scheduled_end_timestamp is not None
    assert wait_2.scheduled_end_timestamp is not None
import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;
import software.amazon.lambda.durable.model.ExecutionStatus;
import software.amazon.awssdk.services.lambda.model.OperationStatus;
import software.amazon.awssdk.services.lambda.model.OperationType;
import software.amazon.lambda.durable.testing.LocalDurableTestRunner;

@Test
void testMultipleWaits() {
    var runner = LocalDurableTestRunner.create(Object.class, new MultipleWaitExample());

    var result = runner.runUntilComplete("test");

    assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus());

    // Find all wait operations
    var waitOps = result.getOperations().stream()
        .filter(op -> op.getType() == OperationType.WAIT)
        .toList();
    assertEquals(2, waitOps.size());

    // Verify both waits have names
    var waitNames = waitOps.stream().map(op -> op.getName()).toList();
    assertTrue(waitNames.contains("wait-1"));
    assertTrue(waitNames.contains("wait-2"));
}

See also