Common Ruby Pitfalls
Ruby's flexibility is a double-edged sword: the same expressiveness that makes it pleasant to write also makes it easy to introduce subtle bugs, especially for developers moving from more rigid languages. This topic walks through the mistakes that show up most often in real codebases and code review — mutable object sharing, string mutation surprises, nil-related crashes, and misunderstanding equality — along with the idiomatic fix for each.
Cricket analogy: A batsman with an unorthodox, flexible technique like Virender Sehwag can score fast but is also prone to getting out to loose shots; Ruby's freedom similarly invites elegant code and subtle bugs alike.
Mutable default arguments and shared state
A frequent bug is initializing a hash or array default value in a way that shares a single object across calls. Unlike def foo(list = []), which correctly creates a fresh array on each call, memoization patterns like @cache ||= {} combined with class-level (rather than instance-level) variables can accidentally share state across all instances of a class. Similarly, calling Array.new(3, []) creates one array object referenced three times — mutating one element mutates all three, because [] is evaluated once and reused as the fill value rather than being called per-slot.
Cricket analogy: Handing three batsmen the exact same borrowed bat instead of issuing each a fresh one means a crack from one player's damage shows up in all three players' equipment simultaneously.
# Pitfall: shared object reference
rows = Array.new(3, [])
rows[0] << "x"
rows # => [["x"], ["x"], ["x"]] -- all three share the same array!
# Fix: use a block, which is called once per slot
rows = Array.new(3) { [] }
rows[0] << "x"
rows # => [["x"], [], []]
# Pitfall: mutating a frozen string literal
# frozen_string_literal: true
name = "ruby"
name << " 3.3" # raises FrozenError under magic comment
# Fix: build a new string instead of mutating
name = "ruby" + " 3.3"
# or explicitly duplicate before mutating
name = "ruby".dup
name << " 3.3"
# Pitfall: nil handling
user = find_user(id)
puts user.name.upcase # NoMethodError if user is nil
# Fix: use safe navigation or explicit checks
puts user&.name&.upcase
puts user.nil? ? "Unknown" : user.name.upcaseString mutation and frozen literals
Since Ruby 3.0, the # frozen_string_literal: true magic comment is common in gems and increasingly the default posture recommended by style guides, because it prevents accidental in-place mutation of string literals and improves performance by avoiding repeated allocation. The pitfall is forgetting that any code calling <<, gsub!, or concat on what looks like an ordinary string literal will raise FrozenError once that comment is present, so bang methods should generally be reserved for strings you know you own and created explicitly (e.g. via String.new or .dup).
Cricket analogy: Marking a bat as tournament-certified means once issued for a match, no one may re-carve or reweight it mid-innings; any attempt to alter it triggers an official's ban, just like FrozenError.
Equality confusion: `==`, `equal?`, and `eql?`
Ruby has three separate equality methods, and mixing them up causes subtle bugs. == checks value equality and is the one to override for custom comparison logic. equal? checks object identity — whether two references point to the literally same object in memory — and should almost never be overridden. eql? is used internally by Hash for key comparison and additionally distinguishes types that == might treat as equal, such as 1 == 1.0 being true while 1.eql?(1.0) is false. Developers who override == but forget eql? and hash can end up with objects that behave inconsistently as hash keys.
Cricket analogy: Comparing two players' batting average (== value equality) differs from asking if they're literally the same physical person (equal? identity); two openers can share an identical average without being the same player.
Unlike Python, where is (identity) and == (equality) are distinct operators with well-known behavior, Ruby has three overlapping methods (==, equal?, eql?) that map roughly to Python's ==, is, and dict-key comparison respectively — but the naming similarity between == and eql? is a common source of confusion for newcomers.
A subtle but classic pitfall: rescuing Exception instead of StandardError. rescue Exception => e also catches things like SystemExit and Interrupt, meaning Ctrl+C or a normal script exit can get silently swallowed. Always rescue StandardError (the default when you write a bare rescue) unless you have a very specific reason not to.
Array.new(3, [])shares one array object across all slots; useArray.new(3) { [] }to get independent objects.- With
# frozen_string_literal: true, calling bang methods like<<on string literals raises FrozenError. - Safe navigation (
&.) avoidsNoMethodErrorwhen a value might benil. ==is value equality,equal?is identity equality, andeql?is used for Hash key comparison and is type-strict.- Rescuing
Exceptioninstead ofStandardErrorcan accidentally swallowSystemExitandInterrupt. - Overriding
==without also updatingeql?andhashcan break Hash key behavior for custom objects.
Practice what you learned
1. What is wrong with `Array.new(3, [])`?
2. What does `# frozen_string_literal: true` change about string literals?
3. What does `equal?` check in Ruby?
4. Why is rescuing `Exception` instead of `StandardError` risky?
5. What is the safe navigation operator used for?
Was this page helpful?
You May Also Like
Ruby Interview Questions
A curated set of common Ruby interview questions and concise, accurate answers spanning language fundamentals, object orientation, and idiomatic style.
Strings and String Methods
How Ruby represents text with the String class, including literal syntax, interpolation, mutability, encoding, and the most commonly used string methods.
Exception Handling in Ruby
Understand Ruby's begin/rescue/ensure/raise vocabulary for handling errors gracefully, including exception hierarchies, retry, and best practices for robust code.
Ruby Quick Reference
A condensed cheat sheet of core Ruby syntax, common Enumerable methods, and idioms for fast lookup while coding.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics