ruby
thread-safety
concurrency
programming
coding-tips

how to know what is NOT thread-safe in ruby?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

There is no short official list of "everything that is not thread-safe in Ruby." The practical rule is more conservative: if an object is mutable and shared across threads, assume it is not safe unless the documentation explicitly says otherwise. Ruby's GVL in MRI reduces some kinds of parallel execution, but it does not turn shared mutable state into a safe design.

Start With the Risk Model

Thread-safety problems usually come from three ingredients:

  • shared state
  • mutation
  • operations that take more than one step

If your code has all three, it deserves suspicion.

For example, this increment is not a single conceptual unit at the Ruby level:

ruby
1counter = 0
2
310.times.map do
4  Thread.new do
5    1_000.times do
6      counter += 1
7    end
8  end
9end.each(&:join)
10
11puts counter

Even if MRI serializes bytecode execution in many moments, the read-modify-write pattern is still logically vulnerable when threads interleave.

Mutable Core Objects Need Care

Arrays, hashes, and strings are common sources of trouble when several threads write to the same instance:

ruby
1items = []
2
3threads = 10.times.map do |i|
4  Thread.new do
5    100.times { items << i }
6  end
7end
8
9threads.each(&:join)
10puts items.length

This kind of code may appear to work in small tests, which is exactly why it is dangerous. Thread-safety bugs often hide until timing changes under load.

The conservative approach is:

  • do not share mutable objects unless necessary
  • protect writes with a Mutex
  • prefer immutable data or per-thread state when possible

Memoization Is a Frequent Trap

This pattern is convenient but not reliably thread-safe:

ruby
def config
  @config ||= load_config
end

Two threads can observe @config as unset and both initialize it. If initialization has side effects or must happen exactly once, add synchronization:

ruby
1@config_mutex = Mutex.new
2
3def config
4  @config_mutex.synchronize do
5    @config ||= load_config
6  end
7end

This is a good example of why "works most of the time" is not the same as thread-safe.

Documentation Beats Assumption

To know whether something is safe, check in this order:

  1. library documentation
  2. implementation notes
  3. issue trackers or known concurrency caveats
  4. source code, if needed

If the docs do not promise thread-safety, do not invent that guarantee yourself.

This matters especially for gems. Some libraries are safe for concurrent reads but not concurrent writes. Others require one object per thread. "Thread-safe" is rarely universal.

Use Explicit Synchronization

Ruby gives you tools to make the contract obvious:

ruby
1mutex = Mutex.new
2balance = 0
3
4threads = 5.times.map do
5  Thread.new do
6    1000.times do
7      mutex.synchronize do
8        balance += 1
9      end
10    end
11  end
12end
13
14threads.each(&:join)
15puts balance

This is slower than unsynchronized mutation, but it is correct. Correctness is the first requirement.

For richer concurrency primitives, libraries such as concurrent-ruby can provide safer data structures and coordination tools.

Common Pitfalls

  • Assuming MRI's GVL makes all object access thread-safe.
  • Sharing mutable hashes, arrays, or instance variables across threads without a lock.
  • Treating memoization with ||= as automatically safe in concurrent code.
  • Testing only under low contention and concluding the code is safe.
  • Forgetting that third-party gems need explicit thread-safety guarantees too.

Summary

  • In Ruby, shared mutable state should be assumed unsafe unless documented otherwise.
  • MRI's GVL does not remove the need for synchronization.
  • Compound operations such as incrementing counters or lazy initialization are common trouble spots.
  • Use Mutex, thread-local data, or thread-safe abstractions when state must be shared.
  • When in doubt, trust documentation and source code over intuition.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.