Collections, blocks and Enumerable
Array, Hash and Set in practice, the Enumerable methods you will use daily, lazy enumerators, symbols versus strings, and destructive methods.
Array, Hash and Set
users = [{ id: 1, name: "Ada", age: 36 }, { id: 2, name: "Grace", age: 45 }]
users.map { |u| u[:name] } # => ["Ada", "Grace"]
users.select { |u| u[:age] > 40 } # subset
users.partition { |u| u[:name].start_with?("A") }
users.sum { |u| u[:age] }
users.group_by { |u| u[:age] / 10 * 10 } # => {30=>[...], 40=>[...]}
users.min_by { |u| u[:age] }
users.index_by { |u| u[:id] } # ActiveSupport; Hash#[]= equivalent below
index = users.each_with_object({}) { |u, acc| acc[u[:id]] = u }
counts = Hash.new(0) # default value, not a default object
%w[a b a].each { |w| counts[w] += 1 } # => {"a"=>2, "b"=>1}
require "set"
seen = Set.new
seen << "a"
seen.include?("a") # O(1), unlike Array#include?each_with_objectis the idiomatic accumulator: the memo is the block's second argument and is returned.Hash.new(0)uses an immutable default;Hash.new { |h, k| h[k] = [] }is needed when the default must be mutable, otherwise every missing key shares one array.- Since Ruby 1.9 a Hash preserves insertion order, so iteration order is deterministic and testable.
- Converting an Array to a Set turns an O(n) membership test inside a loop into an O(1) lookup - the single easiest performance win in Ruby.
💡
Naming signals intent: a trailing
! means a destructive or surprising variant (sort! mutates, sort returns a new array), and a trailing ? means a boolean. Nothing enforces this, so honouring it is what makes Ruby readable.Blocks, procs and lazy enumerators
def each_chunk(list)
return to_enum(:each_chunk, list) unless block_given?
list.each_slice(100) { |slice| yield slice }
end
adder = ->(a, b) { a + b } # lambda: strict arity, returns from itself
doubler = proc { |x| x * 2 } # proc: lenient arity, returns from the method
adder.call(1, 2)
adder.(1, 2)
[1, 2, 3].map(&:to_s) # Symbol#to_proc shorthand
# lazy: the pipeline stops when the consumer stops asking
(1..Float::INFINITY).lazy
.map { |n| n * n }
.select { |n| n.even? }
.first(5) # => [4, 16, 36, 64, 100]
File.foreach("huge.log").lazy
.select { |line| line.include?("ERROR") }
.take(10)
.to_a| Construct | Arity | return means |
|---|---|---|
lambda / -> | Strict | Return from the lambda |
proc | Lenient (fills nil) | Return from the enclosing method |
Block passed with yield | Lenient | Return from the enclosing method |
Symbol#to_proc | One argument | n/a |
A method that wants to accept a block should either call yield or capture it explicitly with &block. To make it return an enumerator when no block is given, use return to_enum(__method__, args) unless block_given?.
Traps worth knowing
a = [1, 2, 3]
b = a # same object
c = a.dup # shallow copy
d = a.map(&:itself) # new array, new values for immutables
prices = { "tea" => 3, "cake" => 5 }
prices.each { |pair| } # pair is the [key, value] array
prices.each { |k, v| } # destructuring, the form you usually want
words = %w[banana Apple cherry]
words.sort # ["Apple", "banana", "cherry"] - byte order, uppercase first
words.sort_by(&:downcase) # case-insensitive
[1, 2, 3].each_with_index.map { |n, i| n * i }
[[1, 2], [3, 4]].to_h
[nil, 1, nil].compactdupandclonecopy the container, not its elements - nested hashes are still shared.String#==compares content whileequal?compares object identity;:symbol == "symbol"is false.freezea constant such asSTATES = %w[...].freeze; an unfrozen array constant can be mutated by any caller.- Prefer
eachplus a clear accumulator over chainedmapandflatten- each intermediate step allocates a full array.
FAQ
Symbols or strings for hash keys?
Symbols for fixed internal keys, because they are interned and cheaper to compare. Use strings for keys that arrive from external data such as JSON or a database row, and convert deliberately at the boundary instead of mixing both.
Why does mutating an array inside each not affect the original?
Because
each yields values, not references into the container. Use map! or each_index to mutate in place, and be aware that mutation during iteration produces surprising results.Related
Syntax and blocks Strings, files and input/output
Last refreshed 2026-09-18.