Hash#fetch

Added in v3.x · Updated March 13, 2026 · Hash Methods
ruby stdlib hash

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

ParameterTypeDefaultDescription
keyObjectRequiredThe key to look up in the hash
defaultObjectnilValue to return if key is not found
blockProcnilBlock 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.

See Also