12 essential data structures for Ruby developers. Each one explained with Big O complexity, animated visuals, and real code samples you can copy.
Array (frozen)Fixed-size, contiguous block of memory. Elements are stored sequentially and accessed by index in constant time. The foundation of most other data structures.
arr[0] = 7
Complexity
When to use
# Ruby arrays are dynamic, but can simulate fixed-size
nums = [10, 20, 30, 40, 50].freeze # immutable reference
copy = nums.dup
copy[0] = 99 # O(1) - direct index access
val = copy[2] # O(1) - read by index
# Search
idx = copy.index(30) # O(n) - linear search
has = copy.include?(40) # O(n) - linear scan
# Sort
sorted = copy.sort # O(n log n)
copy.sort! # O(n log n) - in-placeArrayResizable array that automatically grows when capacity is exceeded. The most commonly used data structure in most languages. Doubles its internal storage when full, giving amortized O(1) appends.
arr[0] = 7
Complexity
When to use
names = ["Alice", "Bob", "Charlie"]
names.push("Diana") # O(1) amortized - append
names.insert(1, "Eve") # O(n) - shifts elements
names.pop # O(1) - remove last
names.shift # O(n) - remove first
has = names.include?("Bob") # O(n)
idx = names.index("Bob") # O(n)
names.sort! # O(n log n) - in-place
squares = (0...10).map { |x| x * x } # O(n) - functional transformArray (push/pop)Last-In-First-Out (LIFO) collection. Only the top element is accessible. Used for tracking state that must be unwound in reverse order.
Push 10
Complexity
When to use
# Use Array as stack
stack = []
stack.push(10) # O(1)
stack.push(20)
stack.push(30)
top = stack.last # O(1) - peek -> 30
val = stack.pop # O(1) - pop -> 30
# Interview pattern: valid parentheses
def valid?(s)
pairs = { "(" => ")", "[" => "]", "{" => "}" }
st = []
s.each_char do |c|
if pairs.key?(c) then st.push(pairs[c])
elsif st.empty? || st.pop != c then return false
end
end
st.empty?
endThread::QueueFirst-In-First-Out (FIFO) collection. Elements are added at the back and removed from the front. Fundamental for breadth-first processing.
Enqueue 10
Complexity
When to use
require "thread"
# Thread::Queue for thread-safe queue
q = Thread::Queue.new
q.push("Task A") # O(1) - enqueue
q.push("Task B")
q.push("Task C")
val = q.pop # O(1) - dequeue (blocks if empty)
q.empty? # check if empty
# Interview pattern: BFS using Array
def bfs(root)
queue = [root]
until queue.empty?
node = queue.shift # O(n) - dequeue
print "#{node.val} "
queue.push(node.left) if node.left
queue.push(node.right) if node.right
end
endHashMaps keys to values using a hash function for near-constant-time lookups. The single most important data structure for coding interviews. Every language has a built-in implementation.
hash("age") = 0
Complexity
When to use
prices = { "apple" => 3, "banana" => 5 }
prices["cherry"] = 2 # O(1) - add
prices["apple"] = 10 # O(1) - update
has = prices.key?("banana") # O(1)
prices.delete("cherry") # O(1)
count = prices.fetch("mango", 0) # O(1) - safe lookup with default
# Interview pattern: Two Sum
def two_sum(nums, target)
seen = {}
nums.each_with_index do |num, i|
need = target - num
return [seen[need], i] if seen.key?(need) # O(1)
seen[num] = i # O(1)
end
[]
endSetUnordered collection of unique elements. Uses hashing internally for O(1) membership testing. Supports mathematical set operations like union, intersection, and difference.
hash("age") = 0
Complexity
When to use
require "set"
s = Set[1, 2, 3, 4, 5]
s.add(6) # O(1)
s.delete(1) # O(1)
has = s.include?(4) # O(1) -> true
# Set operations
other = Set[4, 5, 6, 7]
s & other # intersection -> Set[4, 5, 6]
s | other # union
s - other # difference
# Interview pattern: contains duplicate
def contains_duplicate?(nums)
nums.length != nums.to_set.length
endmanual implSequence of nodes where each node points to the next (singly linked) or both next and previous (doubly linked). Efficient insertion and deletion at any known position, but no index-based access.
traversing: 5
Complexity
When to use
# Ruby has no built-in linked list - manual implementation
ListNode = Struct.new(:val, :next)
head = ListNode.new(1, ListNode.new(2, ListNode.new(3)))
# Traverse - O(n)
curr = head
while curr
print "#{curr.val} "
curr = curr.next
end
# Interview pattern: reverse a linked list
def reverse(head)
prev = nil
curr = head
while curr
nxt = curr.next # save
curr.next = prev # reverse link
prev = curr; curr = nxt
end
prev # O(n) time, O(1) space
endsorted ArrayCollection of unique elements maintained in sorted order, typically backed by a balanced binary search tree (red-black tree). Supports range queries and O(log n) min/max.
search(8)
Complexity
When to use
require "set"
# SortedSet was removed in Ruby 3.0 - use sorted array pattern
sorted = [5, 3, 8, 1, 9].sort # [1, 3, 5, 8, 9]
# Insert maintaining order - O(log n) search + O(n) shift
idx = sorted.bsearch_index { |x| x >= 4 } || sorted.length
sorted.insert(idx, 4) # [1, 3, 4, 5, 8, 9]
sorted.delete(3) # O(n) - remove
min_val = sorted.first # O(1) -> 1
max_val = sorted.last # O(1) -> 9
# Range query
between = sorted.select { |x| x >= 4 && x <= 8 } # [4, 5, 8]sorted HashKey-value pairs maintained in sorted key order, typically backed by a balanced BST. Enables ordered iteration and range lookups that hash maps cannot provide.
search(8)
Complexity
When to use
# Ruby Hash preserves insertion order but not sorted order
sd = { "banana" => 2, "apple" => 5, "cherry" => 1 }
sd["date"] = 3 # O(1) insert
val = sd["apple"] # O(1) -> 5
sd.delete("cherry") # O(1)
# Iterate in sorted key order - O(n log n)
sd.sort_by { |k, _| k }.each do |key, value|
puts "#{key}: #{value}"
end
# apple: 5, banana: 2, date: 3
first_key = sd.keys.sort.first # "apple"
last_key = sd.keys.sort.last # "date"manual MinHeapCollection where elements are dequeued by priority rather than insertion order. Typically implemented as a binary heap. Essential for shortest-path algorithms and top-K problems.
min-heap
Complexity
When to use
# No built-in heap - manual min-heap implementation
class MinHeap
def initialize = @items = []
def push(val) # O(log n)
@items << val; sift_up(@items.size - 1)
end
def pop # O(log n)
return nil if @items.empty?
@items[0], @items[-1] = @items[-1], @items[0]
min = @items.pop; sift_down(0)
min
end
def peek = @items.first # O(1)
private
def sift_up(i)
while i > 0 && @items[i] < @items[(i - 1) / 2]
@items[i], @items[(i - 1) / 2] = @items[(i - 1) / 2], @items[i]
i = (i - 1) / 2
end
end
def sift_down(i) = nil # heapify down
endConcurrent::MapThread-safe hash map designed for concurrent read/write access from multiple threads. Uses fine-grained locking or lock-free techniques instead of a single global lock.
hash("age") = 0
Complexity
When to use
# gem install concurrent-ruby
require "concurrent-ruby"
cache = Concurrent::Map.new
cache["counter"] = 0 # O(1) - thread-safe write
val = cache["counter"] # O(1) - thread-safe read
# Atomic compute-if-absent
cache.compute_if_absent("sessions") { 0 } # factory called once
# Atomic update
cache.compute("counter") { |v| v + 1 } # thread-safe increment
# Merge - update or insert
cache.merge_pair("hits", 1) { |old| old + 1 }
# Safe iteration (snapshot semantics)
cache.each_pair { |k, v| puts "#{k}: #{v}" }String slice / IO::BufferZero-copy view over a contiguous region of memory. Lets you reference a portion of an array or buffer without allocating new memory. Critical for performance-sensitive parsing and processing.
Span[0..3] = [1, 2, 3]
Complexity
When to use
# String slicing - returns a new string (copy)
data = "Hello, World!"
chunk = data[7, 5] # "World" - O(k) copy
data[7, 5] = "Earth" # mutate in place
puts data # "Hello, Earth!"
# Array slicing - returns a new array (copy)
nums = [10, 20, 30, 40, 50]
slice = nums[1, 3] # [20, 30, 40] - O(k) copy
# String#byteslice for byte-level slicing
raw = "binary\x00data"
header = raw.byteslice(0, 6) # O(k) - "binary"
body = raw.byteslice(7..) # O(k) - "data"
# IO::Buffer (Ruby 3.2+) for zero-copy memory views
buf = IO::Buffer.for(raw) # wraps existing string memoryAverage-case time complexity. * = amortized.
| Structure | Access | Search | Insert | Delete |
|---|---|---|---|---|
| Array | O(1) | O(n) | O(n) | O(n) |
| Dynamic Array | O(1) | O(n) | O(1)* | O(n) |
| Stack | O(n) | O(n) | O(1)* | O(1) |
| Queue | O(n) | O(n) | O(1)* | O(1) |
| Hash Map | O(1) | O(1) | O(1)* | O(1) |
| Hash Set | N/A | O(1) | O(1)* | O(1) |
| Linked List | O(n) | O(n) | O(1) | O(1) |
| Sorted Set | O(n) | O(log n) | O(log n) | O(log n) |
| Sorted Map | O(log n) | O(log n) | O(log n) | O(log n) |
| Priority Queue | O(n) | O(n) | O(log n) | O(log n) |
| Concurrent Map | O(1) | O(1) | O(1)* | O(1) |
| Memory View | O(1) | O(n) | N/A | N/A |
| I need to... | Use |
|---|---|
| Store items by index, resize dynamically | List / Dynamic Array |
| Map keys to values with O(1) lookup | HashMap / Dictionary |
| Track unique items, check existence in O(1) | HashSet / Set |
| Last-in-first-out (undo, DFS, brackets) | Stack |
| First-in-first-out (BFS, task queues) | Queue |
| Keep elements sorted at all times | SortedSet / TreeSet |
| Process items by priority (Dijkstra, top-K) | PriorityQueue / Heap |
| Insert/delete at a known position in O(1) | LinkedList |
| Sorted key-value pairs | SortedDictionary / TreeMap |
| Thread-safe shared cache | ConcurrentDictionary |
| Slice arrays/strings without copying | Span / Slice / memoryview |
The most commonly used are dynamic arrays (List/ArrayList/vector), hash maps (Dictionary/HashMap/dict), and hash sets. For interviews, also know stacks, queues, trees, and priority queues. These cover 90%+ of coding interview problems.
Start with the dynamic array and hash map. Together they solve the majority of interview problems. Then learn stacks (for DFS, bracket matching) and queues (for BFS). After that, tackle trees, heaps, and graphs.
No. Big O measures algorithmic complexity, not language-specific performance. A hash map lookup is O(1) whether you use Python dict, Java HashMap, or C# Dictionary. Constant factors differ (C++ is faster than Python in wall-clock time), but Big O is the same.
It depends on the language runtime. Check the Ruby standard library documentation for heap or priority queue support.
Practice with an AI interviewer that asks you to implement, optimize, and explain data structure choices in real time.
Try a free mock interviewarrow_forward