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:
When you pass a class or regex instead of a block, Ruby calls === on that object for each element. Integer === 1 evaluates to true, String === 1 to false — the class itself becomes the match test. This shorthand reads cleanly when the question is “does each element belong to this type or match this pattern?” and works with any object that defines a meaningful ===.
[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:
Writing the block out explicitly makes the === dispatch visible, which helps when you are learning the mechanism behind the shorthand. In production code the class-name-as-argument form is more common because it is terse and the intent is clear at a glance. Both paths call the same underlying method — the difference is purely syntactic, and the runtime behaviour is identical.
[1, 2, 3, 4].find { |n| Integer === n } # => 1
No match: default behaviour
When no element satisfies the condition, find returns nil:
Calling find on an empty collection or against a condition nothing satisfies returns nil without raising an error. This follows the Enumerable convention of returning nil for “not found” rather than throwing an exception, which means you can chain the result with the safe navigation operator &. or use it in a conditional expression without wrapping the call in a begin/rescue block.
[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:
The second argument to find is a callable — a proc, lambda, or any object that responds to call. When find exhausts the collection without finding a match, it invokes that callable and returns its result instead of nil. This lets you provide a meaningful default value, an error message, or a computed fallback object inline, without an extra conditional after the call.
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:
The ifnone callable fires only when no element matches, which means expensive default computations do not run unnecessarily. If find locates a match early — say on the first element of a long array — the default callable is never touched. This lazy behaviour makes the ifnone pattern safe for fallback values that involve database queries, file reads, or object construction that you would rather avoid on the happy path.
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:
Defining the ifnone handler as a lambda keeps the fallback logic separate from the search criteria. The lambda can reference outer variables through closure, so it can build a context-aware default — for example, a “not found” hash that includes the search parameters. This pattern is common in service objects that must return either a real record or a null-object substitute without forcing the caller to check for nil.
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:
detect is the original Smalltalk-inspired name and remains in Ruby for compatibility and style preference. Some teams standardise on find alone; others use detect to distinguish the Enumerable search from ActiveRecord’s find, which raises when a record is missing. Both method names resolve to the same C implementation in MRI, so the choice carries no performance cost — it is purely a readability decision.
[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, including the optional ifnone callable described earlier. Use whichever reads more naturally in your context or matches your team’s conventions.
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)
The best case kicks in when the matching element appears near the start of the collection — find checks only enough elements to locate it and returns. The worst case, paradoxically, is when no element matches at all, because Ruby must inspect every single element to confirm that none satisfies the condition. For sorted data where a specific match is unlikely, consider whether a different data structure or search strategy would serve better than a linear scan.
[1, 2, 3, 4, 5].find { |n| n > 10 } # => nil, checks all five elements
You can observe the iteration with side effects:
Adding a puts call inside the block makes the short-circuit behaviour visible: you can see exactly which elements were inspected before the match was found. This technique is useful for debugging performance problems — if the logged output shows hundreds of checks before a hit, the collection may be ordered in a way that works against early termination. In production code, remove instrumentation blocks like this once you have confirmed the access pattern.
[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
When working with arrays of hashes — a common pattern in Ruby for representing structured data without defining full-blown classes — find with hash-key access inside the block is the most direct way to locate a single record. The block receives each hash in turn, and you reach into it with a symbol key to test the field you care about. The return value is the entire hash, not just the matched field, so you get the full record back.
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
The &:even? shorthand uses Symbol#to_proc to convert the symbol :even? into a block that sends that method name to each element. This works with any zero-argument predicate method — &:odd?, &:nil?, &:zero?, and any custom predicate you define on the elements. It is one of the most recognisable Ruby idioms for concise collection processing and pairs naturally with find when the predicate is already a named method.
numbers = [1, 3, 5, 6, 7, 8]
numbers.find(&:even?) # => 6
With a hash
Calling find on a hash iterates over key-value pairs, yielding a two-element array [key, value] to the block on each iteration. The block can destructure that array with |k, v| or index into it by position. The return value from find on a hash is the matching pair as a two-element array, not a single value or a hash — a detail worth keeping in mind when downstream code expects a particular return shape.
config = { host: "localhost", port: 5432, ssl: false }
config.find { |k, v| v == false } # => [:ssl, false]
Providing a default result
The arrow syntax -> { } creates an anonymous lambda inline, which is more concise than lambda { } and equally valid as an ifnone argument. The lambda captures its surrounding scope through closure, so it can reference constants, local variables, or method return values from the enclosing context. This is useful when the default object depends on runtime information that is not known at the point where the lambda is defined.
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
When your data contains arrays of hashes with predictable keys, find lets you drill into the nested structure right inside the block condition. The early-return behaviour means you do not pay for inspecting deeply nested hashes beyond the first match — if you have a thousand orders and the first one is already in the "shipped" state, find returns immediately after checking a single element.
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
Wrapping find in a method that accepts a default callable gives you a safe, expressive search function. The -> { :no_errors } arrow lambda is a sentinel value — callers can check whether the return value equals :no_errors to determine whether the search succeeded. This pattern avoids the ambiguity of nil returns in situations where find might legitimately locate a nil value stored in the data.
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:
Because find returns a single element (or nil) while select returns an array, the two methods serve different call-site expectations. Code that calls find typically wants the matching element itself for immediate use. Code that calls select expects to iterate over, count, or further transform the result set. Choosing between them is as much about how the calling code consumes the answer as it is about the filtering logic itself — pick the one whose return type matches the downstream expectation.
[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