rubyguides

Hash#key?

The key? method returns true if the given key is present in the hash, false otherwise.

It is a small method, but it shows up everywhere because key checks are a normal part of hash access. You can use it to guard a lookup, distinguish between a missing key and a falsy value, or make an update conditional on the hash already having that entry.

Syntax

hash.key?(key)   # => true or false

Parameters

ParameterTypeDefaultDescription
keyObjectRequiredThe key to look up in the hash

Return Value

Returns true if key exists in the hash, false if it does not.

Description

key? is one of four identical methods for checking key existence on Ruby’s Hash. The others are has_key?, include?, and member?. All four do the same thing — they use the hash’s internal lookup, which is O(1) on average.

Use key? when you want to verify a key is present before accessing or modifying its value. This is safer than direct access with [], which returns nil for missing keys (unless the hash has a default).

That distinction matters when the value itself might be false or nil. In that case, the presence test tells you whether the key exists, while direct lookup only tells you what value is stored there.

Examples

Basic usage

config = { host: "localhost", port: 5432, ssl: true }

config.key?(:host)    # => true
config.key?(:timeout) # => false

The result is easy to read in conditionals, and it keeps the guard logic local to the code that needs it. That makes the next lookup or assignment a little safer without adding much noise. Because the check is isolated in a single line, the reader can confirm that a key is present before any value-dependent logic runs, which is cleaner than mixing existence checks and value tests in the same expression.

Guarding against missing keys

def connect(options)
  raise ArgumentError, "host is required" unless options.key?(:host)

  host = options[:host]
  port = options.fetch(:port, 5432)
  # ...
end

The guard clause makes the method fail fast, which is usually what you want when an option is required. It also keeps the exception message close to the rule being enforced, which is easier to support later.

key? is often paired with fetch when you want to distinguish between a missing key and an explicit nil value:

settings = { debug: false }

if settings.key?(:debug)
  # key exists, even if the value is falsy
  enable_debugging if settings[:debug]
end

This pattern is useful when the mere existence of the key matters more than the truthiness of the value. It lets you treat false and nil as real stored values instead of silently collapsing them into the same outcome. In feature-flag code or configuration handling, this distinction is important because a key set to false means something very different from a key that was never provided at all.

With symbol and string keys

Symbol and string keys are distinct in Ruby hashes:

prefs = { theme: "dark", "font_size" => 14 }

prefs.key?(:theme)        # => true
prefs.key?("theme")       # => false
prefs.key?("font_size")   # => true

The distinction between symbol and string keys is a common source of bugs, so this example helps show that key? compares exactly what is stored in the hash, with no implicit conversion. There is no automatic coercion between the two forms, which means a lookup for "theme" will never match :theme.

Checking before assignment

defaults = { timeout: 30, retries: 3 }
user_options = { timeout: 60 }

def merge_options(defaults, overrides)
  merged = defaults.dup
  overrides.each do |key, value|
    merged[key] = value if merged.key?(key)
  end
  merged
end

merge_options(defaults, user_options)
# => { timeout: 60, retries: 3 }

Only keys that exist in the defaults are taken from overrides.

That pattern is a small but practical way to keep configuration merging strict. It lets you accept changes from the caller without accidentally introducing new keys that the rest of the code does not expect.

Aliases

key? is equivalent to:

  • has_key? — same behavior, more explicit
  • include? — same behavior, older name
  • member? — same behavior, oldest alias
h = { a: 1, b: 2 }

h.key?(:a)        # => true
h.has_key?(:a)    # => true
h.include?(:a)    # => true
h.member?(:a)     # => true

The aliases are useful to know because different codebases prefer different names. Once you know they are identical, you can read whichever version the surrounding code style already uses. The O(1) lookup time is shared across all four method names, so there is no performance reason to pick one over another — it is purely a readability choice that the codebase or style guide should answer consistently.

Gotchas

key? is straightforward, but its simplicity hides a few traps that can surprise developers who are used to treating hashes as simple key-value stores. The two most common ones involve confusing key checks with value checks and misunderstanding how ||= interacts with keys that hold an explicit nil.

Confusing key? with value?. key? checks keys. value? checks values. These are independent lookups:

scores = { alice: 95, bob: 82 }

scores.key?(:alice)   # => true  — :alice is a key
scores.value?(95)     # => true  — 95 is a value
scores.key?(95)       # => false — 95 is not a key
scores.value?(:alice) # => false — :alice is not a value

Using ||= with potentially nil values. If a key exists with an explicit nil value, key? returns true but ||= still overwrites. The ||= operator checks truthiness, not existence, so a key set to nil or false will be silently replaced by the right-hand side. This is a common gotcha in configuration code where a deliberate nil value carries meaning.:

config = { debug: nil }

config.key?(:debug)        # => true
config[:debug] ||= false   # overwrites nil with false — probably not what you want

Use fetch instead when you need to distinguish between a missing key and nil.

That is the main pitfall with key?: it tells you whether the key exists, not whether the stored value is truthy. Once you keep that difference in mind, the method becomes a very reliable part of hash handling.

See Also