Ruby Fibers: Understanding Concurrency and Non-Blocking Execution

binil.sn@pitsolutions.com By binil.sn@pitsolutions.com on July 16, 2026

As computing costs continue to increase, using system resources efficiently has become more important than ever. Every application aims to serve more users, respond faster, and remain reliable without constantly increasing infrastructure costs.

One common challenge is long-running operations such as database queries, API calls, or reading files. During these operations, the application is often just waiting for a response. While it's waiting, the CPU has very little work to do, yet the resources allocated to that task remain occupied. While one task is waiting, the application can continue working on another. This keeps the system productive, improves throughput, and makes better use of the available hardware.

At PIT Solutions, we continuously explore techniques that help us build applications that are both resource-efficient and reliable. In this article, we'll look at one such technique.

 

What Are Ruby Fibers?

Fiber was introduced in Ruby 1.9 and later in Ruby 3.0 with non-blocking fibers and the Fiber Scheduler are introduced.

A Fiber is a lightweight unit of execution in Ruby that allows a task to pause and resume later. Instead of blocking while waiting for an operation such as a database query or an API request to complete, a fiber can yield control so that other work can continue. The goal of fibers isn't to make your code execute faster. Instead, they help make better use of available resources by allowing the runtime to switch to other work whenever a task is waiting. This improves throughput and enables applications to handle more work with the same hardware.

Think of a busy restaurant with a single staff member serving multiple tables. If the staff member waits at one table while customers decide what to order or while the kitchen prepares the food, every other table has to wait. Much of their time is spent waiting instead of serving customers.

Now imagine the staff member takes an order from one table, then moves on to another while the first table is deciding or waiting for the kitchen. Instead of standing idle, they continue serving other tables and return when needed. As a result, more tables are served, customers wait less, and the restaurant makes better use of its staff.

Modern software uses the same idea. Instead of waiting for one task to finish before starting another, it can switch to other work whenever a task is waiting. This keeps the system busy, improves performance, and makes better use of computing resources.

How Concurrency Works in Ruby

Ruby provides three primary concurrency models:

  • Processes – Each process has its own memory space and runs independently. Processes provide true parallelism and are commonly used by web servers like Puma to utilize multiple CPU cores.
  • Threads – Multiple threads share the same process and memory. They are lightweight compared to processes and are well suited for handling many I/O-bound tasks. However, Ruby's Global VM Lock (GVL) allows only one thread to execute Ruby code at a time, although other threads can continue while one is waiting for I/O.
  • Fibers – Fibers are lightweight tasks that help applications make better use of a thread. When one task is waiting for an operation to complete, another task can continue running.

Each model solves a different problem. Processes help applications use multiple CPU cores, threads allow multiple tasks to run concurrently, and fibers make waiting tasks more efficient by making better use of a thread.

Ruby Fibers vs Ruby Threads

At first glance, fibers and threads may seem similar because both allow applications to handle multiple tasks. The key difference lies in how they are scheduled.

A thread is scheduled by the operating system. Once created, the operating system decides when to pause one thread and run another. This is known as preemptive multitasking.

A fiber, on the other hand, is scheduled by the application. A fiber continues running until it voluntarily yields control, allowing another fiber to execute. This is known as cooperative multitasking.

Threads are a good choice when you need multiple tasks to run concurrently and don't want to manage when each task should pause. Fibers are much lighter than threads and are particularly effective for I/O-bound workloads, where tasks spend most of their time waiting for external resources such as databases, files, or network requests.

In practice, threads and fibers are not competing technologies—they often work together. A typical Ruby application may use multiple threads to handle requests, while each thread manages multiple fibers to make better use of waiting time.

Understanding Fiber Scheduler in Ruby 3.0

So far, we've learned that fibers can pause and resume execution. But this raises an important question:

Who decides when a fiber should pause and when it should continue?

Before Ruby 3, a call such as a database query or an HTTP request would block the entire thread until it completed. Even if the application was only waiting for a response, the thread couldn't do any other work.

Ruby 3 introduced the Fiber Scheduler, which allows compatible libraries to pause the current fiber whenever it encounters a blocking I/O operation. While that fiber waits, the scheduler can run another fiber that's ready to execute. Once the I/O operation completes, the original fiber resumes exactly where it left off.

The result is simple: instead of a thread sitting idle while waiting for I/O, it can continue making progress on other tasks. This improves resource utilization and allows applications to handle more concurrent work without creating additional threads.

It's important to note that the Fiber Scheduler doesn't automatically make every application asynchronous. It only works with libraries that support the scheduler, such as compatible HTTP clients, database drivers, and other I/O libraries.

Before Ruby Version 3.0

After Ruby Version 3.0

Non-Blocking Fibers in Ruby Applications

By themselves, fibers are not non-blocking. They can pause and resume execution, but they don't automatically know when an operation is waiting.

This is where the Fiber Scheduler comes in. When a compatible library performs a blocking I/O operation—such as making an HTTP request, querying a database, or reading a file—it notifies the scheduler that it's waiting. The scheduler then pauses the current fiber and allows another ready fiber to run on the same thread.

From your application's perspective, the code remains simple and synchronous:

user = fetch_user

posts = fetch_posts

Behind the scenes, the scheduler can switch between fibers whenever one is waiting for I/O. This allows a single thread to stay productive instead of sitting idle.

It's important to remember that this only works with fiber-aware libraries. If a library doesn't support the Fiber Scheduler, the thread will still block until the operation completes.

Practical Example of Ruby Fiber Execution

Imagine a Rails endpoint that needs to fetch information from three external services before responding.

Each API call takes approximately 500 ms.

Without fibers, the requests are processed one after another.

Now consider a fiber-aware application using a compatible HTTP client and the Fiber Scheduler.

The important thing to notice is that the external services are not faster. Each still takes around 500 ms to respond.

Fibers don't automatically make code concurrent. Frameworks like async create and manage multiple fibers, while the Fiber Scheduler allows those fibers to yield during I/O so other fibers can continue running.

 

Benefits of Using Ruby Fibers for Asynchronous Programming

Ruby fibers help applications make better use of available resources by reducing the time threads spend waiting for I/O operations. This leads to several benefits:

  • Better resource utilization – Keep threads productive instead of idle while waiting.
  • Higher throughput – Handle more requests with the same hardware.
  • Lower memory usage – Fibers are much lighter than threads.
  • Simpler asynchronous code – Write code in a familiar synchronous style while the Fiber Scheduler manages waiting tasks.
  • Improved scalability – Particularly effective for applications that perform frequent database queries or API calls.

Keep in mind that fibers are designed for I/O-bound workloads. They don't speed up CPU-intensive tasks or provide true parallel execution.

 

Ready to Build High-Performance Ruby Application?

Building high-performance applications isn't just about adding more servers. It's about getting the most out of the resources you already have. At P I T Solutions, we continuously optimize both new and existing Ruby applications by leveraging modern Ruby features and proven concurrency techniques to improve performance, reliability, and scalability.