Enumerable#find

Updated April 1, 2026 · Enumerable
ruby enumerable find detect stdlib

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