Hash#fetch
Hash#fetch retrieves the value associated with a given key from a hash. Unlike the bracket notation hash[key], fetch lets you provide a default value or execute a block when the key is missing. This makes it the safest way to look up keys when you are not certain they exist.
Syntax
hash.fetch(key) # raises KeyError if missing
hash.fetch(key, default) # returns default if missing
hash.fetch(key) { |key| block } # returns block result if missing
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
key | Object | Required | The key to look up in the hash |
default | Object | nil | Value to return if key is not found |
block | Proc | nil | Block evaluated with missing key, result returned |
Examples
Basic fetch
user = { name: "Alice", email: "alice@example.com", age: 30 }
user.fetch(:name)
# => "Alice"
user.fetch(:email)
# => "alice@example.com"
KeyError on missing keys
config = { host: "localhost", port: 3000 }
config.fetch(:debug)
# KeyError (KeyError): key not found: :debug
This behavior differs from hash[key] which returns nil for missing keys. Using fetch without a default makes missing keys explicit failures rather than silent nil returns.
Providing a default value
settings = { theme: "dark", language: "en" }
settings.fetch(:font_size, "14px")
# => "14px"
settings.fetch(:theme, "light")
# => "dark"
The default is only used when the key is missing. It never evaluates when the key exists.
Using a block
cache = { users: 100, requests: 500 }
# Block receives the missing key
cache.fetch(:errors) { |k| "No #{k} tracked" }
# => "No :errors tracked"
# Block is ignored when key exists
cache.fetch(:users) { |k| "default" }
# => 100
Blocks are evaluated lazily — only when the key is missing. This is useful for expensive default computations.
Comparison with bracket notation
data = { count: 0, items: [] }
data[:count] # => 0 (returns the value, even if falsy)
data[:missing] # => nil (silent failure)
data.fetch(:count) # => 0 (works with falsy values)
data.fetch(:missing, :default) # => :default
Using fetch with a default is often clearer than the idiomatic hash[key] || default because it distinguishes between a missing key and a nil value.
Common Patterns
Safe configuration lookup
ENV.fetch("RAILS_ENV", "development")
# => "development" (or actual RAILS_ENV if set)
Fetch with computation
prices = { apple: 1.50, banana: 0.75 }
# Only compute discount when key is missing
prices.fetch(:orange) { |k| calculate_price(k) }
Validation with fetch
required_keys = [:name, :email, :password]
data = { name: "Bob", email: "bob@example.com" }
required_keys.each do |key|
data.fetch(key) { |k| raise "Missing required key: #{k}" }
end
# => KeyError: Missing required key: :password
Fetch vs fetch_values
h = { a: 1, b: 2, c: 3 }
# fetch returns one value
h.fetch(:a)
# => 1
# fetch_values returns array of multiple values
h.fetch_values(:a, :c)
# => [1, 3]
Edge Cases
Falsy values work correctly
h = { zero: 0, empty: "", false_val: false }
h.fetch(:zero) # => 0
h.fetch(:empty) # => ""
h.fetch(:false_val) # => false
Bracket notation would return nil for all missing keys, making it impossible to distinguish from nil values.
Symbol vs string keys
hash = { "id" => 123 }
hash.fetch(:id) # KeyError
hash.fetch("id") # => 123
Nested hash access
config = { db: { host: "localhost", port: 5432 } }
config.fetch(:db).fetch(:host)
# => "localhost"
For deep nesting, consider Hash#dig instead.