Fibers & Lazy Evaluation

Reviewed & published by Brayan K

A Fiber is a block of code you can pause with Fiber.yield and resume with resume, cooperatively passing control and values back and forth between the fiber and its caller.

Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll use fibers to build lazy, on-demand sequences and see how Ruby's Enumerator is powered by them.

What You'll Learn in This Lesson

1️⃣ Pause and Resume with Fibers

Create a fiber with Fiber.new { ... }. Calling resume runs it until it hits Fiber.yield, which pauses it and returns a value to the caller. The next resume picks up right after that yield — local variables and all.

# A Fiber is a block of code you can PAUSE and RESUME at will.
# Fiber.yield pauses; Fiber#resume continues from where it left off.
fib = Fiber.new do
  puts "start"
  Fiber.yield "first pause"     # hands control back to the caller
  puts "resumed"
  Fiber.yield "second pause"
  puts "finishing"
  "done"                        # the final value of resume
end

puts fib.resume   # runs to the first Fiber.yield
puts fib.resume   # continues to the second Fiber.yield
puts fib.resume   # runs to the end

2️⃣ Two-Way Communication

Fibers are coroutines: Fiber.yield x sends x out to the caller, and resume(y) sends y back in, becoming the return value of that yield. The first resume usually just primes the fiber up to its first pause.

# resume can pass a value INTO the fiber; Fiber.yield returns it.
# This makes a fiber a two-way, cooperative coroutine.
adder = Fiber.new do
  total = 0
  loop do
    n = Fiber.yield total    # pause, returning the running total;
    total += n               #   when resumed, n is the value passed in
  end
end

adder.resume                 # prime it: run up to the first yield (total = 0)
puts adder.resume(10)        # add 10 -> 10
puts adder.resume(5)         # add 5  -> 15
puts adder.resume(100)       # add 100 -> 115

3️⃣ Lazy Sequences & the Enumerator Connection

Because a fiber produces one value per resume, it's a natural generator: you can model an infinite stream and only compute as far as you pull. Ruby's Enumerator is built on this exact mechanism — enum.next resumes an internal fiber to produce the next item.

# Building a lazy sequence with a Fiber: produce values only on demand.
def naturals_fiber
  Fiber.new do
    n = 1
    loop do
      Fiber.yield n     # produce the next number, then pause
      n += 1
    end
  end
end

gen = naturals_fiber
first_five = 5.times.map { gen.resume }
p first_five

# Enumerator is built ON TOP of fibers — .next uses one under the hood.
enum = Enumerator.new do |y|
  y << "a"
  y << "b"
  y << "c"
end
puts enum.next     # internally resumes a fiber
puts enum.next
puts enum.peek     # look without advancing
puts enum.next

🎯 Your Turn

The fiber should pause after each color. Replace each ___ with the call that pauses the fiber and hands a value to the caller.

# 🎯 YOUR TURN — fill in the ___ so the fiber pauses on each color.
colors = Fiber.new do
  ___ "red"      # 👉 which call pauses the fiber and returns a value?
  ___ "green"
  ___ "blue"
end

puts colors.resume   # should print red
puts colors.resume   # should print green
puts colors.resume   # should print blue

# ✅ Expected output:
#    red
#    green
#    blue

Common Errors (and the fix)

Pro Tips

Mini-Challenge: A Lazy ID Generator

Build a fiber-based ID generator that yields zero-padded IDs on demand, and run two independent generators side by side. Run with ruby ids.rb.

# 🎯 MINI-CHALLENGE: a lazy ID generator using a Fiber.
# Save as ids.rb and run with: ruby ids.rb
#
# 1. Write id_generator(prefix) returning a Fiber.
# 2. Each resume yields "PREFIX-001", "PREFIX-002", ... (zero-padded to 3).
# 3. Create TWO independent generators and interleave their resumes.
#
# ✅ Expected output:
#    USER-001
#    USER-002
#    ORD-001
#    USER-003
#    ORD-002

# your code here

📋 Quick Reference — Fibers

GoalRuby
Create a fiberFiber.new { ... }
Pause & send outFiber.yield value
Resume & send infiber.resume(arg)
Generator APIEnumerator.new { |y| ... }
Pull next valueenum.next / enum.peek
vs threadscooperative, no parallelism

🎉 Lesson Complete!

Practice quiz

How do you create a Fiber?

  • Fiber.start { ... }
  • Fiber.run { ... }
  • Fiber.new { ... }
  • fiber { ... }

Answer: Fiber.new { ... }. You create a fiber with Fiber.new { ... } and run it with resume.

What pauses a fiber from inside its block?

  • Fiber.yield
  • yield
  • Fiber.pause
  • return

Answer: Fiber.yield. Fiber.yield pauses the fiber and hands control (and a value) back to the caller.

What method continues a paused fiber?

  • fiber.next
  • fiber.call
  • fiber.yield
  • fiber.resume

Answer: fiber.resume. fiber.resume continues from where the fiber last paused.

How is a fiber different from a thread?

  • Fibers run in parallel
  • Fibers are cooperative — they only advance when resumed, with no preemption
  • Threads can't share data
  • Fibers are preemptive

Answer: Fibers are cooperative — they only advance when resumed, with no preemption. A fiber only runs between resume and the next yield; it is cooperative, so there is no preemption or race conditions.

What happens if you resume a fiber that has already finished?

  • It raises FiberError (dead/terminated fiber)
  • It returns nil
  • It restarts from the top
  • It blocks forever

Answer: It raises FiberError (dead/terminated fiber). Resuming a finished fiber raises FiberError about a dead/terminated fiber.

How does a value get INTO a fiber?

  • Through Fiber.yield's argument
  • Via a global variable
  • As the return value of Fiber.yield, via resume(arg)
  • You can't pass values in

Answer: As the return value of Fiber.yield, via resume(arg). resume(arg) sends arg in; it becomes the return value of the Fiber.yield that paused the fiber.

Which Ruby class is built on top of fibers internally?

  • Array
  • Enumerator
  • Hash
  • Proc

Answer: Enumerator. Enumerator uses a fiber internally; enum.next resumes it to produce the next value.

Why does the first resume of a two-way fiber often just 'prime' it?

  • To allocate memory
  • To check for errors
  • To start a thread
  • To run it up to its first yield before you feed values in

Answer: To run it up to its first yield before you feed values in. The first resume runs the fiber to its first pause; later resumes can then send values in.

Can fibers achieve true parallelism on their own?

  • Yes, always
  • No — only one fiber runs at a time
  • Only on multiple cores
  • Only with a mutex

Answer: No — only one fiber runs at a time. Fibers are cooperative and run one at a time; for parallelism you reach for threads.

Inside a Fiber block, using the plain `yield` keyword instead of `Fiber.yield` will...

  • Pause the fiber correctly
  • Raise immediately
  • Fail to pause the fiber (it's the block keyword, not the fiber pause)
  • Resume the caller

Answer: Fail to pause the fiber (it's the block keyword, not the fiber pause). Plain yield calls the block; you must use Fiber.yield to actually pause the fiber.

Continue this course

Frequently asked questions

What is a Fiber in Ruby?

A Fiber is a lightweight unit of code that you can pause and resume cooperatively. You create one with Fiber.new { ... }, run it with fiber.resume, and pause it from the inside with Fiber.yield. Unlike a thread, a fiber never runs on its own — it only advances when something explicitly resumes it, so there is no preemption and no race conditions. This makes fibers ideal for building generators, lazy sequences, and state machines where you want to suspend computation midway and pick it up later.

How is a fiber different from a thread?

A thread is scheduled by the operating system and can be interrupted at any moment, so two threads sharing data can collide and you need locks. A fiber is scheduled by your own code: it runs only between a resume and the next Fiber.yield, and control passes back and forth deterministically. Fibers are therefore cheaper and simpler than threads but cannot achieve parallelism on their own — only one fiber runs at a time. Use threads for overlapping I/O or true concurrency, and fibers for cooperative, predictable suspension.

How do fibers relate to Enumerator and lazy evaluation?

Ruby's Enumerator uses a fiber internally to implement external iteration. When you call enum.next, Ruby resumes a fiber that runs the enumerator's block until the next yielded value, then pauses it again — which is exactly how a generator produces one item at a time without computing the whole sequence. That same pause/resume mechanism is what lets lazy enumerators and fiber-based generators model infinite streams: values are produced on demand, only as far as the consumer pulls them.

Can I pass values into and out of a fiber?

Yes — fibers are two-way. Inside the fiber, Fiber.yield value sends value out as the return of the current resume call. Outside, fiber.resume(arg) sends arg in, and it becomes the return value of the Fiber.yield that paused the fiber. This bidirectional flow turns a fiber into a coroutine: the caller and the fiber take turns, each handing data to the other. The first resume usually 'primes' the fiber by running it up to its first yield before you start feeding values in.