Enumerable#find
What does find do?
find returns the first element in a collection that satisfies the given condition. If no element matches, it returns nil by default, or the result of calling an ifnone callable if one is provided.
The method iterates through each element in order and stops as soon as it finds a match — this is called short-circuit evaluation. It doesn’t scan the entire collection unnecessarily.
Basic Usage
With a block
[1, 2, 3, 4, 5].find { |n| n > 3 } # => 4
[1, 2, 3, 4, 5].find { |n| n > 10 } # => nil
With a predicate argument
You can pass a pattern object instead of a block. The method calls === on the pattern for each element:
[1, 2, 3, 4].find(Integer) # => 1
[1, 2, 3, 4].find(String) # => nil
words = ["apple", "banana", "cherry"]
words.find(/^b/) # => "banana"
words.find(/^z/) # => nil
This is equivalent to:
[1, 2, 3, 4].find { |n| Integer === n } # => 1
No Match: Default Behaviour
When no element satisfies the condition, find returns nil:
[1, 2, 3].find { |n| n > 100 } # => nil
[].find { |x| x > 0 } # => nil
The ifnone Argument
You can pass a callable (a proc, lambda, or any object that responds to call) as the second argument. This is invoked when no element is found instead of returning nil:
result = [1, 2, 3].find({ "No match found" }) { |n| n > 10 }
result # => "No match found"
The ifnone argument is evaluated lazily — it’s only called when needed:
default = -> { raise "Should not be called!" }
[1, 2, 3].find(default) { |n| n == 2 } # => 2 (default is never invoked)
A common pattern is to use a lambda for the default:
not_found = -> { { id: nil, name: "Unknown" } }
users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
users.find(not_found) { |u| u[:id] == 99 } # => { id: nil, name: "Unknown" }
detect: The Alias
find and detect are identical — detect is simply an alias:
[1, 2, 3].find { |n| n > 1 } # => 2
[1, 2, 3].detect { |n| n > 1 } # => 2
Both methods share the same implementation and accept the same arguments. Use whichever reads more naturally in your context.
Short-Circuit Evaluation
find stops iterating as soon as it finds a match. This has two important implications:
Best case: O(1)
[1, 2, 3, 4, 5].find { |n| n > 1 } # => 2, checks only first two elements
Worst case: O(n)
[1, 2, 3, 4, 5].find { |n| n > 10 } # => nil, checks all five elements
You can observe the iteration with side effects:
[1, 2, 3, 4].find do |n|
puts "Checking #{n}"
n > 2
end
# Output:
# Checking 1
# Checking 2
# Checking 3
# => 3
Practical Examples
Finding a user by attribute
users = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "editor" },
{ id: 3, name: "Carol", role: "viewer" }
]
users.find { |u| u[:role] == "admin" }
# => { id: 1, name: "Alice", role: "admin" }
Finding the first even number
numbers = [1, 3, 5, 6, 7, 8]
numbers.find(&:even?) # => 6
With a hash
config = { host: "localhost", port: 5432, ssl: false }
config.find { |k, v| v == false } # => [:ssl, false]
Providing a default result
products = [
{ name: "Widget", price: 100 },
{ name: "Gadget", price: 200 }
]
premium = products.find(-> { { name: "None", price: 0 } }) { |p| p[:price] > 500 }
# => { name: "None", price: 0 }
Finding in nested structures
orders = [
{ id: 1, status: "pending", items: ["a", "b"] },
{ id: 2, status: "shipped", items: ["c"] },
{ id: 3, status: "pending", items: ["d", "e", "f"] }
]
orders.find { |o| o[:status] == "shipped" }
# => { id: 2, status: "shipped", items: ["c"] }
Early exit in data processing
def find_first_error(results)
results.find(-> { :no_errors }) { |r| r[:status] == :error }
end
find_first_error([
{ status: :ok, data: "row1" },
{ status: :ok, data: "row2" },
{ status: :error, data: "row3" },
{ status: :ok, data: "row4" }
])
# => { status: :error, data: "row3" }
find vs select
find returns only the first matching element, while select returns all matching elements as an array:
[1, 2, 3, 4, 5].find { |n| n > 2 } # => 3 (single value)
[1, 2, 3, 4, 5].select { |n| n > 2 } # => [3, 4, 5] (array)
Use find when you only need one result. Use select when you need all matches.
See Also
- /reference/enumerable/enumerable-any/ — Returns true if any element satisfies the condition
- /reference/enumerable/enumerable-none/ — Returns true if no element satisfies the condition
- /reference/enumerable/enumerable-include/ — Checks if a value exists in the collection