rubyguides

Hash#filter

hash.filter { |key, value| block } -> hash

filter returns a new hash containing only the key-value pairs where the block returns a truthy value.

Syntax

hash.filter { |key, value| condition }

Parameters

ParameterTypeDefaultDescription
blockProcRequiredA block receiving key and value. Return truthy to keep the key-value pair in the result, falsy to discard it from the output hash.

Examples

Basic usage

scores = { alice: 95, bob: 82, charlie: 78, diana: 90 }

# Keep only scores above 85
passing = scores.filter { |name, score| score > 85 }
# => {:alice=>95, :diana=>90}

The block receives both the key and the value for each entry, giving you full flexibility over the filtering logic. You can test the key, the value, or any combination of the two. When you only need one of them, prefix the unused parameter with a leading underscore — this is a Ruby convention that signals to readers and linters that the parameter is intentionally ignored.

Filtering by key

config = { host: "localhost", port: 8080, debug: true, ssl: false }

# Keep only string values
string_vals = config.filter { |_key, value| value.is_a?(String) }
# => {:host=>"localhost"}

Filtering by key type or key pattern is common when you need to separate configuration categories or extract a subset of fields from a larger structure. Using a leading _ prefix on block parameters keeps the intent clear — the reader can see at a glance whether the filter cares about keys, values, or both. This pattern also works well with is_a?, regex matches, and custom predicate methods.

Practical example

user_input = { name: "Alice", age: "25", email: "alice@example.com", phone: nil }

# Keep only non-empty values
cleaned = user_input.reject { |_key, value| value.nil? || value.to_s.strip.empty? }
# => {:name=>"Alice", :age=>"25", :email=>"alice@example.com"}

This example uses reject to remove entries with nil or empty values, which is the inverse of what filter would do with the same predicate. Both methods return a new hash, so neither modifies the original input. That immutability is valuable when the source data needs to stay intact for later use, such as when multiple downstream consumers each need a different subset of the same starting structure.

Common Patterns

Chaining with transform

data = { a: 1, b: 2, c: 3, d: 4, e: 5 }

filtered_then_doubled = data.filter { |k, v| v > 2 }.transform_values { |v| v * 2 }
# => {:c=>6, :d=>8, :e=>10}

Chaining filter with transform_values is a clean two-step pipeline: first narrow the entries, then adjust what remains. Because each step returns a new collection, the chain reads left to right exactly as the data flows through it. This composability is one of the main reasons to prefer filter over a manual loop — the code says what it does without spelling out the iteration machinery.

Read the block as a predicate

filter keeps the pairs for which the block returns a truthy value, so the block should read like a yes-or-no question. That small mental model helps when you are scanning code later: the block is deciding whether each pair belongs in the result. Because the method returns a new hash, you can chain it with other transformations without affecting the original input. That is especially useful when you are working with settings, form data, or any structure where you want to preserve the source values.

Prefer the clearer twin when needed

Ruby also gives you select, which behaves the same way for hashes. In practice, teams often pick one name and stick with it so the codebase reads consistently. If the surrounding code already uses filter, keep using it; if it uses select, that is fine too. The important part is that the predicate stays easy to read. Short, direct conditions are easier to debug than long boolean chains, especially when the hash contains many different value types.

Keep the result shape in mind

The output hash preserves the entries that passed the test, but it does not copy nested data for you. If a selected value is itself a mutable object, both hashes still point at the same underlying object. That matters when you later update a nested structure and expect the original to stay frozen in place. Treat filter as a way to narrow the view of the data, not as a deep-clone tool. That distinction keeps the method useful without creating surprises.

See Also