Enumerable#include?
What does include? do?
include? checks whether the collection contains at least one element that equals the argument. It returns true if a match is found, false otherwise.
The method uses the == operator for comparison, not ===. This distinction matters when working with custom objects or specialized types like ranges.
Basic Usage
[1, 2, 3].include?(2) # => true
[1, 2, 3].include?(5) # => false
["apple", "banana"].include?("apple") # => true
With a range:
(1..10).include?(5) # => true
(1..10).include?(15) # => false
With a string:
"hello".include?("lo") # => true
"hello".include?("x") # => false
== vs === Comparison
include? uses ==, not ===. This is a common source of confusion.
# Using == (what include? actually uses)
(1..5).include?(3) # => true (1 == 3 is false, but 3 == 3..5 is true via Range#==)
# What you might expect with ===
(1..5) === 3 # => true (Range#=== checks membership)
(1..5) === 7 # => false
For most built-in types this difference is subtle, but it becomes important when you define custom equality semantics.
Performance Considerations
The performance of include? depends on the collection type:
Arrays — O(n) linear search
# Each element is checked sequentially until a match is found
large_array = (1..1_000_000).to_a
large_array.include?(999_999) # Slow — checks every element until the end
large_array.include?(1) # Fast — finds it immediately
For large arrays, consider using a Set instead if you need frequent membership tests.
Sets — O(1) constant time
require "set"
large_set = Set.new(1..1_000_000)
large_set.include?(999_999) # Fast — uses hash lookup
large_set.include?(1) # Fast
Hashes — O(1) for key lookup
{ a: 1, b: 2 }.include?(:a) # => true
{ a: 1, b: 2 }.include?(:c) # => false
Ranges — O(1) for numeric ranges
# Ruby's Range#include? checks the bounds, not every element
(1..1_000_000).include?(500_000) # => true
Using with Custom Objects
For include? to work with your own objects, your class must implement the == method:
class Task
attr_reader :name
def initialize(name)
@name = name
end
def ==(other)
other.is_a?(Task) && name == other.name
end
end
tasks = [Task.new("Write"), Task.new("Review")]
tasks.include?(Task.new("Write")) # => true
tasks.include?(Task.new("Publish")) # => false
Without ==, include? falls back to identity comparison (same object in memory).
The member? Alias
include? has an alias called member?. They behave identically:
[1, 2, 3].include?(2) # => true
[1, 2, 3].member?(2) # => true
(10..20).include?(15) # => true
(10..20).member?(15) # => true
Use whichever reads better in context. member? can feel more natural when describing set membership:
roles.include?(:admin) # => "Does this user have the admin role?"
roles.member?(:admin) # => reads like set membership
Practical Examples
Checking for a value in a list
ALLOWED_EXTENSIONS = [".rb", ".rake", ".gemspec"]
def process_file(filename)
ext = File.extname(filename)
if ALLOWED_EXTENSIONS.include?(ext)
puts "Processing #{filename}"
else
puts "Skipping #{filename} — disallowed extension"
end
end
process_file("script.rb") # => Processing script.rb
process_file("data.csv") # => Skipping data.csv — disallowed extension
Guard clause pattern
def register(email)
return if BLACKLISTED_EMAILS.include?(email)
# ... registration logic
end
Conditional logic
valid_statuses = %i[draft published archived]
def update_status(id, new_status)
if valid_statuses.include?(new_status)
# update the record
else
raise ArgumentError, "Unknown status: #{new_status}"
end
end
See Also
- /reference/enumerable/enumerable-tally/ — Count occurrences of each element
- /reference/enumerable/enumerable-group-by/ — Group elements by a condition
- /reference/enumerable/enumerable-reduce/ — Reduce an enumerable to a single value