rubyguides

Array#find

arr.find { |element| block } -> obj or nil

.find iterates through an array or enumerable and returns the first element for which the block evaluates to truthy. If no element matches, it returns nil. This method is essential for searching collections when you need the actual element rather than its index or position.

Syntax

array.find { |element| block } -> obj or nil

The block receives each element in turn and the method returns the first one that makes the block evaluate to a truthy value. If the block never returns truthy, find returns nil without raising an error. This makes it safe to use on collections of unknown size and content.

Parameters

ParameterTypeDefaultDescription
blockProcRequiredA block that receives each element and returns truthy for matching elements

Examples

Basic usage

numbers = [1, 3, 5, 7, 9]

numbers.find { |n| n > 4 }
# => 5

numbers.find { |n| n > 10 }
# => nil

The first call finds 5 because it is the earliest element greater than 4. The second call returns nil because no element exceeds 10. This binary contract — an object on success, nil on failure — is the core behavior you can rely on in every find call.

Finding a hash in an array

users = [
  { name: "Alice", age: 30 },
  { name: "Bob", age: 25 },
  { name: "Charlie", age: 35 }
]

users.find { |user| user[:age] > 28 }
# => { name: "Alice", age: 30 }

When searching through hashes, the block can reference any key you care about. The condition user[:age] > 28 picks Alice because she appears first and satisfies the age threshold. The block returns the entire hash, not just the matched value, so the caller gets the full record.

Using with strings

words = ["apple", "banana", "cherry", "date"]

words.find { |word| word.start_with?("b") }
# => "banana"

words.find { |word| word.length > 10 }
# => nil

The string example shows the same contract: "banana" matches because it starts with "b", while the second call returns nil because no word exceeds ten characters. The method works the same way regardless of the element type, which makes it predictable across different collections.

Common Patterns

Finding by attribute

products = [
  { id: 1, name: "Widget", price: 100 },
  { id: 2, name: "Gadget", price: 50 },
  { id: 3, name: "Gizmo", price: 75 }
]

# Find product by ID
products.find { |p| p[:id] == 2 }
# => { id: 2, name: "Gadget", price: 50 }

This pattern is common in lookup-heavy code where you store records by an identifier and need to retrieve them by that same identifier. The block compares the :id field and the method returns the full record, ready for further processing or display.

Safe navigation with find

results = []

found = results.find { |x| x > 5 }
# Returns nil safely, no error

# Common pattern: use || with default value
found = results.find { |x| x > 5 } || "no match"
# => "no match"

The || operator after find is a convenient way to provide a fallback. When the collection is empty or no element matches, the expression evaluates the right side and supplies a sensible default. This keeps the search and the default close together, which makes the intent easy to scan.

Chaining with other enumerable methods

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# First even number
numbers.select(&:even?).find { |n| n > 5 }
# => 6

Handling missing matches

find is convenient because it gives you the matching object directly, but it also means the caller has to think about nil. That is not a drawback so much as part of the contract: the method says “I looked, and nothing matched” without raising an error. In practice, that makes it a good fit for lookups where a default or fallback value makes sense. Keep the block focused on the condition you really care about, and handle the nil case close to where the search happens.

It also helps to keep the predicate small and specific. A clear condition is easier to read than a long block that mixes matching logic with transformation or reporting. If you need the index instead of the element, another method may be a better fit, but when the actual object is the thing you want, find keeps the search direct and readable. That makes it useful in lookup tables, configuration lists, and any place where the first match should win.

When you chain find with a fallback, the code stays easy to scan because the search and the default live right next to each other. That pattern is especially nice in command lookup code, where a missing match should lead to a simple message or a default object instead of a crash. The method keeps the intent obvious: search first, then decide what to do if nothing matches.

If the search starts to need more than one condition, it can be worth moving the predicate into a small helper so the block stays readable. That lets the calling code keep the simple find shape while the details live in one named place. Small lookups stay clear, and more complex searches still have a tidy home.

See Also