Ruby provides a convenient way to represent sequences of values using Ranges. These can be created using either two dots (..) or three dots (...), and while both create valid Range objects, they behave differently in important ways.

Basic Range Syntax

Both notations create valid Range objects in Ruby:

(1..5).class # => Range
(1...5).class # => Range
("a".."z").class # => Range
("a"..."z").class # => Range

The Key Difference: Inclusivity

The fundamental difference between these notations lies in how they handle the end value:

  • Two dots (..) creates an inclusive range that includes both the start and end values
  • Three dots (...) creates an exclusive range that includes the start value but excludes the end value

Pretty neat. Here is an example of it in action:

(1..10).to_a
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
(1...10).to_a
# => [1, 2, 3, 4, 5, 6, 7, 8, 9]

("a".."j").to_a
# => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
("a"..."j").to_a
# => ["a", "b", "c", "d", "e", "f", "g", "h", "i"]

So there are a number of ways you can use this. Like how you access the data in an array:

array = ['a', 'b', 'c', 'd', 'e']
array[0..2]  # => ["a", "b", "c"]
array[0...2] # => ["a", "b"]

Or how many numbers you iterate through:

# Print numbers 1 through 5
(1..5).each { |n| puts n }  # Prints 1, 2, 3, 4, 5

# Print numbers 1 through 4
(1...5).each { |n| puts n } # Prints 1, 2, 3, 4

Date ranges are a big and useful one.

require 'date'

# Inclusive date range (includes both Jan 1 and Jan 31)
(Date.new(2024,1,1)..Date.new(2024,1,31))

# Exclusive date range (excludes Jan 31)
(Date.new(2024,1,1)...Date.new(2024,1,31))

Recap

  • Use .. when you want to include the end value (e.g., counting from 1 to 10 inclusive)
  • Use ... when you want to exclude the end value (e.g., array slicing or zero-based counting)