Ruby for Coding Interviews: The Enumerable That Does Everything

- Ruby is not allowed at Google but works at Amazon, Meta, Stripe, and most companies with a 'use any language' policy
- Enumerable methods like
tally,group_by, andeach_with_objectreplace dozens of lines of Java boilerplate in one line - Hash.new(0) is the idiomatic frequency counter;
Hash.new { |h,k| h[k] = [] }builds adjacency lists without any initialization - No native heap: implement a 25-line
MinHeapclass inline for Dijkstra and merge-K-lists problems nilis falsy but0is not: useh.fetch(:a, default)andh.key?(:a)instead ofh[:a] || default- Modulo takes the sign of the divisor in Ruby, so
-13 % 4 == 3, unlike Java/C/Go where the result is-1 - Integer overflow is impossible: Ruby's
Bignumhandles arbitrary precision automatically, so(lo + hi) / 2is always safe
Ruby is a weird choice to bring to a coding interview. It runs about ten times slower than Java, Google won't allow it, and the standard library is missing a heap. A heap. Not some esoteric data structure. A heap. And yet, if you know it cold, Ruby might be the most expressive language in the room. One line of group_by replaces fifteen lines of Java boilerplate. That trade-off is worth understanding before you decide.
Should You Actually Use Ruby?
Google does not allow Ruby. Their coding interviews accept Java, C++, JavaScript, and Python only. Full stop. Don't ask. Don't try. They mean it.
For most other companies, you're fine. LeetCode supports Ruby. HackerRank supports it. The typical "use any language you're comfortable with" policy at Amazon, Meta, Stripe, and Airbnb includes Ruby without issue.
The practical risk isn't policy. It's support. Stack Overflow answers skew heavily toward Python and Java. If you get stuck during prep, you'll find fewer Ruby solutions to reference. That costs time while practicing, not during the interview itself.
Use Ruby if it's your primary language, you work at a Rails shop with an implementation-heavy interview, or you know Enumerable cold. Avoid it if you're targeting Google, aren't already fluent, or face graph-heavy problems requiring a priority queue frequently. The language choice guide covers the full trade-off across all common options.
Data Structures at a Glance
| Structure | Ruby class | Require? | Lookup | Insert | Delete |
|---|---|---|---|---|---|
| Dynamic array | Array | built-in | O(n) | O(1) amortized (end) / O(n) front | O(1) end / O(n) front |
| Hash map | Hash | built-in | O(1) avg | O(1) avg | O(1) avg |
| Hash set | Set | require 'set' | O(1) avg | O(1) avg | O(1) avg |
| Sorted map | none | no stdlib | O(log n) | O(log n) | O(log n) |
| Min/max heap | none | no stdlib | O(1) peek | O(log n) | O(log n) |
The two missing rows are the pain points. Ruby has no TreeMap and no heap. You'll implement those yourself or work around them.
Array: More Powerful Than It Looks
Ruby Array is backed by a C array with amortized O(1) appends:
a = [3, 1, 4, 1, 5] a.push(9) # append, O(1) a.pop # remove last, O(1) a.unshift(0) # prepend, O(n), avoid in hot paths a.shift # remove first, O(n) a.first / a.last # peek without removal
Use flatten(1), not flatten. The bare form recurses deeply. If you have an array of pairs and call flatten, you get a flat list when you wanted an array of arrays. You will make this mistake at least once. Usually at 11pm.
[[1, [2]], [3]].flatten # => [1, 2, 3] deep [[1, [2]], [3]].flatten(1) # => [1, [2], 3] one level only
Ruby's sort is stable and idiomatic with sort_by:
words.sort_by { |w| [-w.length, w] } # length descending, then alpha
The two-criteria sort via array comparison is cleaner than any Comparator object. combination and permutation are also built in and return lazy enumerators:
[1, 2, 3].combination(2).to_a # => [[1,2],[1,3],[2,3]] [1, 2, 3].permutation(2).to_a # => [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]]
Hash: Your Default Tool for Everything
Ruby's Hash is ordered by insertion since Ruby 1.9. Under the hood it's open-addressed with the O(1) average guarantees covered in hash map time complexity.
Hash.new(0) is the idiomatic frequency counter. No need to initialize keys:
freq = Hash.new(0) "mississippi".chars.each { |c| freq[c] += 1 } # => {"m"=>1, "i"=>4, "s"=>4, "p"=>2}
The default value block form handles adjacency lists just as cleanly:
graph = Hash.new { |h, k| h[k] = [] } graph[1] << 2 graph[1] << 3
A few methods you'll reach for often:
h.any? { |k, v| v > 2 } h.min_by { |k, v| v } h.transform_values { |v| v * 2 } h.select { |k, v| v > 1 }
transform_values returns a new hash. Use transform_values! to mutate in place.
Set: Use It, But Know Its Limits
Set is not automatically available. You must require 'set' at the top. On LeetCode the require is sometimes implicit. Do not rely on that.
Set#include? is O(1). Array#include? is O(n). For membership checks on large collections, use Set or Hash keys, not Array.
In practice, Hash keys often work just as well and avoid the require:
seen = {} seen[val] = true seen.key?(val) # O(1), no import needed
The Missing Heap
There is no heap in Ruby's standard library. There is an open feature request as of 2025, but it hasn't shipped yet. So whenever you need one, you get to build it yourself. In an interview. From scratch. Against the clock. Fun.

Ruby's stdlib whenever you ask for a priority queue.
For Dijkstra, merge K sorted lists, or any problem requiring repeated smallest/largest access, you have two options.
Option 1: Sort as a workaround. Fine for small inputs or when you only pop once.
Option 2: Implement a minimal binary heap inline. Here is one in 25 lines. Memorize it before the interview:
class MinHeap def initialize @data = [] end def push(val) @data << val bubble_up(@data.size - 1) end def pop return nil if @data.empty? swap(0, @data.size - 1) val = @data.pop bubble_down(0) val end def peek = @data.first def size = @data.size def empty? = @data.empty? private def bubble_up(i) while i > 0 parent = (i - 1) / 2 break if @data[parent] <= @data[i] swap(parent, i) i = parent end end def bubble_down(i) loop do left = 2 * i + 1 right = 2 * i + 2 smallest = i smallest = left if left < @data.size && @data[left] < @data[smallest] smallest = right if right < @data.size && @data[right] < @data[smallest] break if smallest == i swap(i, smallest) i = smallest end end def swap(i, j) = @data[i], @data[j] = @data[j], @data[i] end
For a max-heap, invert the comparisons. For heap-by-key, store [priority, value] pairs and compare on [0]. For a full explanation of why this works, the heap data structure deep dive has the full picture.
Enumerable: Ruby's Interview Superpower
Enumerable is mixed into Array, Hash, Set, and anything that implements each. It maps directly to common interview transformations, and the number of problems it collapses to two lines is honestly embarrassing for every other language.
group_by is the fastest path to "group elements by X":
# Anagram grouping (LC 49) in two lines words.group_by { |w| w.chars.sort.join }
tally (Ruby 2.7+) counts occurrences without a loop:
"mississippi".chars.tally # => {"m"=>1, "i"=>4, "s"=>4, "p"=>2}
each_with_object builds a result while iterating, without an external accumulator variable:
[1, 2, 3, 4].each_with_object({}) { |n, h| h[n] = n ** 2 } # => {1=>1, 2=>4, 3=>9, 4=>16}
reduce/inject folds to a single value:
[1, 2, 3, 4, 5].reduce(:+) # => 15
flat_map maps then flattens one level. Common in graph problems when expanding neighbors. min_by / max_by return the element, not the comparison value:
["banana", "kiwi", "fig"].min_by(&:length) # => "fig"
zip pairs parallel sequences:
[1, 2, 3].zip([4, 5, 6]) # => [[1,4],[2,5],[3,6]]
Five Gotchas That Will Burn You
1. Modulo takes the sign of the divisor.
-13 % 4 # => 3 (positive, sign of 4)
In Java, C, C++, and Go: (-13) % 4 == -1. Test modulo on negatives explicitly. The Dutch National Flag wrapping trick behaves differently across languages for exactly this reason.
2. Integer division floors toward negative infinity.
-7 / 2 # => -4 (floored, not truncated) # Java/C/Go: -7 / 2 == -3
3. No integer overflow. Ever.
Ruby integers grow to Bignum automatically. (lo + hi) / 2 never overflows. That one is actually nice. Use Float::INFINITY for sentinels:
min_cost = Float::INFINITY max_val = -Float::INFINITY
4. nil is falsy; 0 is not.
This one gets everyone who comes from Python, where 0 is falsy:
h = {a: 0} h[:a] || 5 # => 5 WRONG, 0 is falsy, so this returns 5 h.fetch(:a, 5) # => 0 correct
h.key?(:a) is the right existence check, not h[:a].
5. flatten is deep by default. Always pass a depth argument. flatten(1) for one level, flatten for everything.
Quick Reference
Float::INFINITY / -Float::INFINITY a, b = b, a # swap freq = Hash.new(0) # frequency counter freq = arr.tally # Ruby 2.7+ adj = Hash.new { |h, k| h[k] = [] } # adjacency list arr.sort_by { |x| [-x.length, x] } # multi-criteria sort arr.min_by { |x| fn(x) } arr.min(k) # k smallest, unsorted words.group_by { |w| w.chars.sort.join } # group anagrams arr.reduce(:+) seen = {}; seen[x] = true; seen.key?(x) # set via hash "hello".chars # => ["h","e","l","l","o"] ["h","e","l","l","o"].join # => "hello"
Practice It Under Pressure
Knowing Ruby's built-ins is one thing. The interview test is identifying which method fits the problem in real time, narrating your reasoning, and handling follow-up questions on complexity. Reading documentation does not build that fluency. Voice-based mock interviews do, because they put you in the condition you are training for. SpaceComplexity runs rubric-based DSA mocks with spoken feedback across all four scoring dimensions, including the communication and complexity analysis that text-based grinding misses.
The path: nail Enumerable until it's automatic, implement the 25-line MinHeap once so you have it memorized, and practice talking through your solution out loud.
Further Reading
- Ruby Enumerable documentation: the definitive reference for every method in the module
- Ruby Array documentation: complete method listing with complexity notes
- Feature request: native Ruby heap: the open issue tracking a built-in priority queue
- GeeksforGeeks: Data structures in Ruby: worked examples of common structures
- Tech Interview Handbook: programming language guide: language choice analysis across all major options