EffectTalk
Back to Tour
concurrencyBeginner

Understanding Fibers

Learn what fibers are, how they differ from threads, and why they make Effect powerful for concurrent programming.

Guideline

Fibers are Effect's lightweight threads. They're cheap to create (thousands are fine), automatically managed, and can be interrupted cleanly.


Rationale

Unlike OS threads:

  1. Lightweight - Create thousands without performance issues
  2. Cooperative - Yield control at effect boundaries
  3. Interruptible - Can be cancelled cleanly
  4. Structured - Parent fibers manage children

Good Example

import { Effect, Fiber } from "effect"

// ============================================
// WHAT IS A FIBER?
// ============================================

// A fiber is a running effect. When you run an effect,
// it executes on a fiber.

const myEffect = Effect.gen(function* () {
  yield* Effect.log("Hello from a fiber!")
  yield* Effect.sleep("100 millis")
  return 42
})

// This runs myEffect on the "main" fiber
Effect.runPromise(myEffect)

// ============================================
// FORKING: Create a new fiber
// ============================================

const withFork = Effect.gen(function* () {
  yield* Effect.log("Main fiber starting")
  
  // Fork creates a new fiber that runs independently
  const fiber = yield* Effect.fork(
    Effect.gen(function* () {
      yield* Effect.log("Child fiber running")
      yield* Effect.sleep("200 millis")
      yield* Effect.log("Child fiber done")
      return "child result"
    })
  )
  
  yield* Effect.log("Main fiber continues immediately")
  yield* Effect.sleep("100 millis")
  yield* Effect.log("Main fiber waiting for child...")
  
  // Wait for the forked fiber to complete
  const result = yield* Fiber.join(fiber)
  yield* Effect.log(`Got result: ${result}`)
})

Effect.runPromise(withFork)
/*
Output:
Main fiber starting
Child fiber running
Main fiber continues immediately
Main fiber waiting for child...
Child fiber done
Got result: child result
*/

// ============================================
// FIBER OPERATIONS
// ============================================

const fiberOps = Effect.gen(function* () {
  const fiber = yield* Effect.fork(
    Effect.gen(function* () {
      yield* Effect.sleep("1 second")
      return "done"
    })
  )
  
  // Check if fiber is done (non-blocking)
  const poll = yield* Fiber.poll(fiber)
  yield* Effect.log(`Poll result: ${poll}`) // None (still running)
  
  // Wait for completion
  const result = yield* Fiber.join(fiber)
  yield* Effect.log(`Join result: ${result}`)
  
  // Or interrupt if taking too long
  // yield* Fiber.interrupt(fiber)
})

Fiber vs Thread

Aspect OS Thread Effect Fiber
Memory ~1MB stack ~few KB
Creation Expensive Cheap
Scheduling OS kernel Effect runtime
Interruption Messy Clean
Count Hundreds Thousands

Key Operations

Operation What it does
Effect.fork(effect) Start effect on new fiber
Fiber.join(fiber) Wait for fiber to complete
Fiber.interrupt(fiber) Cancel the fiber
Fiber.poll(fiber) Check status without waiting
Fiber.await(fiber) Get Exit (success or failure)

Mental Model

Think of fibers as "lightweight async tasks":

Main Fiber
    
    ├── fork ──► Child Fiber 1 ──► runs independently
    
    ├── fork ──► Child Fiber 2 ──► runs independently
    
    └── join ◄── waits for children
Understanding Fibers | EffectTalk | EffectTalk