Overview
Ruby implements for loops as syntactic sugar over the each
method, providing a familiar iteration construct for developers coming from other languages. The for loop operates on any object that responds to each
, including arrays, hashes, ranges, and custom enumerable objects.
The basic for loop syntax follows the pattern for variable in collection
. Ruby transforms this into a method call to each
on the collection object. Unlike traditional for loops in languages like C or Java, Ruby's for loop does not use numeric indices or increment counters directly.
# Basic for loop over an array
for item in [1, 2, 3, 4, 5]
puts item
end
# => 1
# => 2
# => 3
# => 4
# => 5
# For loop over a range
for num in 1..3
puts "Number: #{num}"
end
# => Number: 1
# => Number: 2
# => Number: 3
# For loop over a hash
for key, value in {name: "Alice", age: 30}
puts "#{key}: #{value}"
end
# => name: Alice
# => age: 30
The for loop creates a new scope for the iteration variable, but unlike block iterators, variables defined inside a for loop remain accessible after the loop completes. This scoping behavior distinguishes for loops from their each
method equivalents.
Basic Usage
For loops work with any enumerable collection in Ruby. The most common usage involves arrays, ranges, and hashes. The syntax supports single variables for simple collections and multiple variables for collections that yield multiple values per iteration.
# Iterating over array elements
fruits = ["apple", "banana", "orange"]
for fruit in fruits
puts "I like #{fruit}"
end
# => I like apple
# => I like banana
# => I like orange
# Working with array indices using each_with_index
for fruit, index in fruits.each_with_index
puts "#{index}: #{fruit}"
end
# => 0: apple
# => 1: banana
# => 2: orange
# Range iteration with step
for i in (0..10).step(2)
puts "Even number: #{i}"
end
# => Even number: 0
# => Even number: 2
# => Even number: 4
# => Even number: 6
# => Even number: 8
# => Even number: 10
Hash iteration requires understanding how Ruby handles key-value pairs. When iterating over a hash with a single variable, Ruby assigns an array containing the key-value pair. Using two variables unpacks the key and value automatically.
person = {name: "Bob", city: "New York", profession: "Engineer"}
# Single variable receives [key, value] array
for pair in person
puts "Pair: #{pair.inspect}"
end
# => Pair: [:name, "Bob"]
# => Pair: [:city, "New York"]
# => Pair: [:profession, "Engineer"]
# Multiple variables unpack key and value
for key, value in person
puts "The #{key} is #{value}"
end
# => The name is Bob
# => The city is New York
# => The profession is Engineer
String iteration works through the each_char
method, though this requires explicit method chaining since strings don't respond to each
by default.
# Iterate over string characters
for char in "Hello".each_char
puts "Character: #{char}"
end
# => Character: H
# => Character: e
# => Character: l
# => Character: l
# => Character: o
Common Pitfalls
Several gotchas arise when using for loops in Ruby, particularly around variable scoping, method dependencies, and performance characteristics. Understanding these issues prevents common mistakes and debugging difficulties.
Variable scoping in for loops differs significantly from block-based iteration. Variables defined inside for loops remain accessible after loop completion, which can lead to unintended variable persistence and namespace pollution.
# Variable scope demonstration
numbers = [1, 2, 3]
# For loop - variable persists
for n in numbers
doubled = n * 2
puts doubled
end
puts "doubled variable after loop: #{doubled}" # => 6
# Each block - variable scoped to block
numbers.each do |n|
tripled = n * 3
puts tripled
end
# puts tripled # => NameError: undefined local variable
For loops fail when collections don't respond to the each
method. This creates runtime errors that aren't immediately obvious from the syntax.
# Common mistake - trying to iterate over non-enumerable
number = 42
# for digit in number # => NoMethodError: undefined method `each' for 42:Integer
# Correct approach - convert to enumerable first
for digit in number.to_s.each_char
puts "Digit: #{digit}"
end
# => Digit: 4
# => Digit: 2
Nested for loops create multiple levels of variable persistence, making variable tracking complex and error-prone.
# Nested loop variable confusion
matrix = [[1, 2], [3, 4], [5, 6]]
for row in matrix
for cell in row
processed = cell * 10
puts processed
end
# Both 'row', 'cell', and 'processed' remain accessible here
puts "Last processed: #{processed}" # Refers to last iteration
end
# All variables still accessible here
puts "Final row: #{row.inspect}" # => [5, 6]
puts "Final cell: #{cell}" # => 6
puts "Final processed: #{processed}" # => 60
Type coercion issues arise when for loops receive unexpected data types. Ruby attempts to call each
on whatever object follows the in
keyword, leading to runtime failures if the object doesn't support iteration.
# Subtle type issue
data = "not_an_array"
# This works - strings respond to each_char
for char in data.each_char
puts char
end
# This fails - strings don't respond to each directly
# for item in data # => NoMethodError
Performance & Memory
For loops carry performance implications due to their implementation as method calls to each
. The transformation adds method call overhead compared to optimized block-based iteration, though the difference remains minimal for most applications.
Memory usage patterns differ between for loops and block iteration. For loops maintain variables in the outer scope, potentially extending object lifetimes beyond the iteration period. This affects garbage collection timing and memory pressure in long-running processes.
# Memory usage comparison
require 'benchmark'
large_array = (1..100_000).to_a
# For loop timing
time_for = Benchmark.measure do
total = 0
for num in large_array
total += num
end
end
# Each block timing
time_each = Benchmark.measure do
total = 0
large_array.each do |num|
total += num
end
end
puts "For loop time: #{time_for.real}"
puts "Each block time: #{time_each.real}"
# Results vary, but each is typically faster
Object allocation patterns show differences in variable lifecycle management. For loops keep iteration variables allocated after completion, while block variables become eligible for garbage collection immediately after block exit.
# Object lifecycle demonstration
def memory_test_for(array)
for item in array
processed = item.to_s * 1000 # Large string allocation
end
# 'processed' remains in scope, preventing GC
GC.start
ObjectSpace.count_objects[:T_STRING]
end
def memory_test_each(array)
array.each do |item|
processed = item.to_s * 1000 # Large string allocation
end
# 'processed' out of scope, eligible for GC
GC.start
ObjectSpace.count_objects[:T_STRING]
end
test_array = [1, 2, 3, 4, 5]
for_strings = memory_test_for(test_array)
each_strings = memory_test_each(test_array)
puts "Strings after for loop: #{for_strings}"
puts "Strings after each block: #{each_strings}"
# Each typically shows fewer allocated strings
Performance optimization strategies focus on minimizing object creation and method calls within loops. For loops benefit from the same optimization techniques as other iteration methods.
# Optimized for loop patterns
data = (1..10_000).to_a
# Avoid repeated method calls
# Inefficient
for item in data
result = expensive_calculation(item.to_s.upcase.strip)
end
# Efficient
for item in data
str_item = item.to_s
upper_item = str_item.upcase
result = expensive_calculation(upper_item.strip)
end
Reference
Syntax Patterns
Pattern | Example | Description |
---|---|---|
for var in collection |
for x in [1,2,3] |
Single variable iteration |
for var1, var2 in collection |
for k, v in hash |
Multiple variable unpacking |
for var in collection.method |
for c in str.each_char |
Method chaining |
Compatible Collection Types
Type | Method Required | Example Usage |
---|---|---|
Array | each (built-in) |
for item in [1, 2, 3] |
Hash | each (built-in) |
for k, v in {a: 1, b: 2} |
Range | each (built-in) |
for i in 1..5 |
String | each_char , each_line |
for c in "abc".each_char |
Enumerator | each (built-in) |
for x in (1..3).each |
Custom Objects | each implementation |
for item in custom_enum |
Variable Scoping Rules
Context | Variable Accessibility | Garbage Collection |
---|---|---|
For loop variable | Accessible after loop | Delayed until scope exit |
Variables defined in loop | Accessible after loop | Delayed until scope exit |
Block local variables | Not accessible after block | Immediate after block |
Equivalent Transformations
For Loop | Each Equivalent | Notes |
---|---|---|
for x in arr |
arr.each {|x| ... } |
Basic transformation |
for x, y in hash |
hash.each {|x, y| ... } |
Multiple assignment |
for i in (1..n) |
(1..n).each {|i| ... } |
Range iteration |
Performance Characteristics
Aspect | For Loop | Each Block | Winner |
---|---|---|---|
Method call overhead | Higher | Lower | Each |
Variable scope management | Complex | Simple | Each |
Memory usage | Higher (persistent vars) | Lower | Each |
Readability | Familiar syntax | Ruby idiomatic | Each |
Debugging | Variables persist | Clean scope | Each |
Error Types
Error | Cause | Solution |
---|---|---|
NoMethodError: undefined method 'each' |
Object doesn't implement each |
Use enumerable or convert object |
ArgumentError: wrong number of arguments |
Variable count mismatch | Match variables to yielded values |
LocalJumpError |
Control flow issues | Avoid break /next outside loops |