Overview
Ruby provides two range operators for creating range objects that represent sequences of values. The inclusive range operator (..
) includes both the start and end values, while the exclusive range operator (...
) includes the start value but excludes the end value. Range objects serve as efficient representations of sequences without storing every intermediate value in memory.
inclusive_range = 1..5
# => 1..5 (represents 1, 2, 3, 4, 5)
exclusive_range = 1...5
# => 1...5 (represents 1, 2, 3, 4)
char_range = 'a'..'z'
# => "a".."z" (represents all lowercase letters)
The Range
class implements these operators and provides methods for iteration, membership testing, and conversion to arrays. Ranges work with any objects that implement the <=>
spaceship operator and succ
method, including integers, floats, strings, and custom classes.
Ruby ranges support both finite and infinite sequences. Infinite ranges use nil
as the end value and require careful handling to avoid infinite loops during iteration.
infinite_range = 1..nil
# => 1.. (represents all integers starting from 1)
backwards_infinite = nil..10
# => ..10 (represents all integers up to 10)
Basic Usage
Creating ranges requires two operands and either the inclusive or exclusive operator. The operands must be comparable objects that respond to the <=>
method.
# Numeric ranges
numbers = 10..20
decimals = 1.5..3.7
negative_range = -10..-1
# Character ranges
letters = 'A'..'Z'
lowercase = 'a'..'z'
# String ranges (lexicographic order)
words = 'apple'..'zebra'
Range membership testing uses the include?
method for discrete ranges and cover?
method for continuous ranges. The include?
method iterates through all values, while cover?
performs boundary checking without iteration.
range = 1..10
range.include?(5)
# => true
range.include?(15)
# => false
# For non-discrete ranges, use cover?
float_range = 1.0..10.0
float_range.cover?(5.7)
# => true
# include? fails on non-discrete ranges with many values
float_range.include?(5.7) # Very slow - avoid
Converting ranges to arrays materializes all values, which consumes memory proportional to the range size. Use to_a
for small finite ranges only.
small_range = 1..5
small_range.to_a
# => [1, 2, 3, 4, 5]
letter_range = 'x'..'z'
letter_range.to_a
# => ["x", "y", "z"]
# Dangerous with large ranges
large_range = 1..1000000
# large_range.to_a # Creates million-element array
Iteration through ranges using each
generates values on demand without storing them in memory.
(1..3).each { |n| puts n * 2 }
# 2
# 4
# 6
# Exclusive range iteration
(1...4).each { |n| puts n }
# 1
# 2
# 3
Advanced Usage
Range creation supports complex objects that implement the required comparison and succession methods. Custom classes must define <=>
for ordering and succ
for generating the next value.
class Version
include Comparable
attr_reader :major, :minor, :patch
def initialize(version_string)
@major, @minor, @patch = version_string.split('.').map(&:to_i)
end
def <=>(other)
[major, minor, patch] <=> [other.major, other.minor, other.patch]
end
def succ
Version.new("#{major}.#{minor}.#{patch + 1}")
end
def to_s
"#{major}.#{minor}.#{patch}"
end
end
version_range = Version.new('1.0.0')..Version.new('1.0.3')
version_range.each { |v| puts v }
# 1.0.0
# 1.0.1
# 1.0.2
# 1.0.3
Ranges combine with enumerable methods for powerful data processing patterns. Methods like select
, map
, and reduce
work directly on range objects without array conversion.
# Filter even numbers from range
evens = (1..20).select(&:even?)
# => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# Transform range values
squares = (1..5).map { |n| n ** 2 }
# => [1, 4, 9, 16, 25]
# Lazy evaluation for large ranges
large_evens = (1..Float::INFINITY).lazy.select(&:even?).first(5)
# => [2, 4, 6, 8, 10]
Range construction using variables and expressions enables dynamic range creation based on runtime conditions.
def create_range(start, length, inclusive = true)
endpoint = start + length - 1
inclusive ? (start..endpoint) : (start...endpoint + 1)
end
dynamic_range = create_range(10, 5)
# => 10..14
dynamic_exclusive = create_range(10, 5, false)
# => 10...15
Ranges work as hash keys and set elements because they implement proper equality and hash methods.
range_counts = {}
range_counts[1..5] = 100
range_counts[6..10] = 200
range_counts[1..5]
# => 100
# Range sets
range_set = Set.new([1..3, 4..6, 1..3])
range_set.size
# => 2 (duplicate range removed)
Common Pitfalls
The distinction between inclusive and exclusive ranges creates frequent confusion, especially with off-by-one errors in calculations and iterations.
# Common mistake: expecting same behavior
inclusive = 1..5
exclusive = 1...5
inclusive.to_a.length
# => 5 elements [1, 2, 3, 4, 5]
exclusive.to_a.length
# => 4 elements [1, 2, 3, 4]
# Array indexing confusion
array = ['a', 'b', 'c', 'd', 'e']
array[1..3] # Elements at indices 1, 2, 3
# => ["b", "c", "d"]
array[1...3] # Elements at indices 1, 2 (excludes 3)
# => ["b", "c"]
String ranges behave lexicographically, not numerically, leading to unexpected results with multi-character strings.
# Surprising string range behavior
range = '1'..'10'
range.include?('2')
# => true
range.include?('9')
# => false (because '9' > '10' lexicographically)
# Correct approach for numeric strings
numeric_range = 1..10
numeric_range.include?(9)
# => true
Infinite ranges require explicit termination conditions to prevent infinite loops during iteration.
# Dangerous - infinite loop
infinite = 1..nil
# infinite.each { |n| puts n } # Never terminates
# Safe infinite range usage
infinite.lazy.first(5)
# => [1, 2, 3, 4, 5]
(1..nil).lazy.select(&:even?).first(3)
# => [2, 4, 6]
Range creation with incompatible types raises ArgumentError
at iteration time, not creation time.
# Range creation succeeds
bad_range = 1..'z'
# => 1.."z"
# Error occurs during iteration
begin
bad_range.each { |x| puts x }
rescue ArgumentError => e
puts e.message
# => can't iterate from Integer
end
Performance degrades significantly when using include?
on continuous ranges instead of cover?
.
require 'benchmark'
large_range = 1.0..1000000.0
test_value = 500000.7
# Slow - iterates through all values
Benchmark.measure { large_range.include?(test_value) }
# => Very slow execution
# Fast - boundary comparison only
Benchmark.measure { large_range.cover?(test_value) }
# => Nearly instantaneous
Reference
Range Creation Operators
Operator | Type | Description | Example |
---|---|---|---|
start..end |
Inclusive | Includes both start and end values | 1..5 represents [1,2,3,4,5] |
start...end |
Exclusive | Includes start, excludes end value | 1...5 represents [1,2,3,4] |
start.. |
Infinite | Starts at value, continues infinitely | 1.. represents [1,2,3,...] |
..end |
Infinite | Continues to end from negative infinity | ..10 represents [...,8,9,10] |
Core Range Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#include?(obj) |
obj (Object) |
Boolean |
Tests membership by iteration through values |
#cover?(obj) |
obj (Object) |
Boolean |
Tests if object falls within range boundaries |
#each {block} |
Block | Range or Enumerator |
Iterates through all range values |
#to_a |
None | Array |
Converts range to array of all values |
#size |
None | Integer or nil |
Returns number of elements (finite ranges only) |
#first(n=1) |
n (Integer) |
Object or Array |
Returns first n elements |
#last(n=1) |
n (Integer) |
Object or Array |
Returns last n elements |
Range Properties
Method | Returns | Description |
---|---|---|
#begin |
Object |
Returns range start value |
#end |
Object |
Returns range end value |
#exclude_end? |
Boolean |
Returns true for exclusive ranges |
#empty? |
Boolean |
Tests if range contains no elements |
#infinite? |
Symbol or nil |
Returns :begin , :end , or nil for infinite ranges |
Range Comparison
Method | Parameters | Returns | Description |
---|---|---|---|
#==(other) |
other (Range) |
Boolean |
Tests range equality |
#===(obj) |
obj (Object) |
Boolean |
Case equality (uses cover? ) |
#eql?(other) |
other (Range) |
Boolean |
Strict equality including type |
Common Range Patterns
Pattern | Usage | Example |
---|---|---|
Array slicing | array[range] |
array[1..3] |
Case statements | case value when range |
when 18..65 |
Loop conditions | for i in range |
for i in 1..10 |
Lazy iteration | range.lazy.method |
(1..∞).lazy.select(&:even?) |
Error Conditions
Error | Cause | Solution |
---|---|---|
ArgumentError |
Incompatible range operands | Use comparable objects of same type |
TypeError |
Range operand lacks <=> method |
Implement Comparable module |
NoMethodError |
Missing succ method for iteration |
Define successor method |
SystemStackError |
Infinite loop in custom succ |
Ensure proper progression in succ |