The Yield Keyword in SimPy: Why It Matters

If you've looked at any SimPy code, you've seen yield everywhere. It's not decoration—it's the mechanism that makes simulation work.

What Is Yield?

yield turns a function into a generator. Instead of running to completion, the function pauses at each yield and can resume later.

def counter():
    yield 1
    yield 2
    yield 3

gen = counter()
print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3

Each next() runs until the next yield, returns that value, then pauses.

Why SimPy Uses Generators

SimPy needs processes that: - Start, pause, and resume - Hand control back to the simulator - Remember their state between pauses

Generators do exactly this. Naturally. Elegantly.

def process(env):
    print("Step 1")
    yield env.timeout(5)   # Pause here
    print("Step 2")
    yield env.timeout(5)   # Pause here
    print("Step 3")

SimPy runs this generator, advancing through each yield. The function maintains its state—local variables, position—between yields.

Yield vs Return

return ends a function. yield pauses it.

def using_return():
    return 1
    return 2  # Never reached
    return 3  # Never reached

def using_yield():
    yield 1
    yield 2  # Reached on second call
    yield 3  # Reached on third call

In SimPy, processes use yield to pause and return to end:

def process(env):
    yield env.timeout(5)
    yield env.timeout(5)
    return "Completed"  # Process ends, returns value

What Happens at Yield

When a SimPy process yields:

  1. Control returns to SimPy - The process pauses
  2. Event is registered - SimPy schedules when to resume
  3. Time can advance - Other events may fire
  4. Process resumes - When the event occurs

This is cooperative multitasking. Processes voluntarily yield control.

Yield vs Yield From

Python has both:

# yield - return a single value
yield env.timeout(5)

# yield from - delegate to another generator
yield from subprocess(env)

In SimPy, both work but yield from is useful for sub-processes:

def step1(env):
    yield env.timeout(3)
    print("Step 1 done")

def step2(env):
    yield env.timeout(2)
    print("Step 2 done")

def main_process(env):
    yield from step1(env)
    yield from step2(env)
    print("All done")

yield from runs the sub-generator completely before continuing.

Receiving Values

Yield can receive values:

def process(env):
    result = yield env.process(other_process(env))
    print(f"Got: {result}")

The yielded event's value is returned when resuming. This is how you get return values from sub-processes.

Common Mistakes

Forgetting Yield

def broken_process(env):
    env.timeout(5)  # Creates timeout but doesn't wait!
    print("This prints immediately")

Without yield, the timeout is created but ignored. The process doesn't pause.

Yielding Non-Events

def broken(env):
    yield 5  # Error! Must yield events

You must yield SimPy events (timeouts, requests, processes), not arbitrary values.

Using Return Too Early

def process(env):
    return env.timeout(5)  # Returns generator, doesn't wait

This returns immediately with a timeout object, rather than waiting.

Generator State

The beauty of generators is they maintain state:

def process(env):
    counter = 0
    while True:
        yield env.timeout(1)
        counter += 1
        print(f"Counter: {counter}")

counter persists between yields. No globals needed. No class state. Just local variables.

Debugging Generators

Print statements help:

def process(env):
    print(f"Starting at {env.now}")
    yield env.timeout(5)
    print(f"After first timeout at {env.now}")
    yield env.timeout(5)
    print(f"After second timeout at {env.now}")

You'll see exactly when execution pauses and resumes.

Performance

Generators are efficient. Suspending and resuming is cheap—much cheaper than threads. A SimPy simulation can have thousands of active processes without performance issues.

The Mental Model

Think of yield as "wait here for this event":

def customer(env, counter):
    yield counter.request()        # Wait for counter
    yield env.timeout(5)           # Wait 5 time units
    # Continue after both waits

Reading yield as "wait for" makes SimPy code intuitive.

Advanced: Generator Send

SimPy uses generator .send() to pass values back:

def demo():
    received = yield 1
    print(f"Got: {received}")
    received = yield 2
    print(f"Got: {received}")

gen = demo()
print(next(gen))      # 1
print(gen.send("A"))  # Prints "Got: A", then 2
gen.send("B")         # Prints "Got: B"

SimPy uses this to return event values when resuming processes. You rarely need to know this—it just works.

Summary

The yield keyword: - Turns functions into generators - Pauses execution until an event occurs - Maintains state between pauses - Is the foundation of SimPy's process model

Understand yield. Understand generators. SimPy becomes obvious.

Next Steps


Discover the Power of Simulation

Want to become a go-to expert in simulation with Python? The Complete Simulation Bootcamp will show you how simulation can transform your career and your projects.

Explore the Bootcamp