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
| Parameter | Type | Default | Description |
|---|---|---|---|
key | Object | Required | The 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 explicitinclude?— same behavior, older namemember?— 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
hash-has-key— the more explicit aliashash-has-value— check if a value exists insteadhash-keys— returns all keys in the hash