How do ranges work in Ruby?

Short answer

1..5 includes 5; 1...5 stops at 4. Ranges are enumerable, so every Enumerable method works on them, and they can be used for slicing and in case expressions.

ruby
(1..5).to_a       # [1, 2, 3, 4, 5]
(1...5).to_a      # [1, 2, 3, 4]
('a'..'e').to_a   # ["a", "b", "c", "d", "e"]

The three-dot form is easy to misread. Many Ruby developers avoid it except where excluding the end is clearly the point, such as 0...array.length.

#As iterators

ruby
(1..5).each  { |i| puts i }
(1..5).map   { |i| i * i }
(1..100).select(&:even?).sum

#For slicing

ruby
letters = %w[a b c d e]

letters[1..3]     # ["b", "c", "d"]
letters[1...3]    # ["b", "c"]
letters[2..]      # ["c", "d", "e"]  — endless range
letters[..2]      # ["a", "b", "c"]  — beginless range
letters[-2..]     # ["d", "e"]

#In case expressions

ruby
grade = case score
        when 90.. then "A"
        when 80...90 then "B"
        when 70...80 then "C"
        else "Keep going"
        end

case uses ===, and for a range that means "does it cover this value". This is the cleanest way to write a banded lookup in Ruby.

#Membership

ruby
(1..10).include?(5)     # true — for ranges of any type
(1..10).cover?(5)       # true — faster, compares endpoints only

Use cover? for numbers and dates. include? may iterate the whole range, which is slow on a large one and impossible on an endless one.

#Infinite ranges are lazy

ruby
(1..).lazy.map { |n| n * 2 }.first(5)     # [2, 4, 6, 8, 10]

Without .lazy that would never finish.