Ruby Dictionaries vs. Hashes (Syntax & Data Specs)
Ruby has no separate built-in Dictionary class. The built-in Hash is Ruby’s key-value collection, used for the same general purpose as a dictionary in other languages. Hash literals can use => or label-style syntax, and key type matters: {name: "Ada"} uses the symbol :name, not the string "name".
Ruby’s collections are easy to misread if you are coming from Python, JavaScript, or another language that uses the word “dictionary.” The key point is simple: in Ruby, the relevant built-in type is Hash. Knowing its syntax and rules helps you avoid bugs when reading data, building options, or passing values between parts of an application.
In the examples below, I focus on core Ruby behavior rather than a particular framework. Check your project’s declared Ruby version when you need version-specific details, and test the behavior your code depends on.
Ruby Hash Overview: Dictionary Concept, Syntax, and Version Context
A Ruby Hash is a collection that associates keys with values. “Dictionary” describes that general data-structure role; it is not a separate built-in Ruby class. Hashes preserve the order in which keys are added, and Ruby provides literal syntax for creating them.
What “dictionary” means in Ruby
A dictionary is a general term for a structure that looks up a value by a key. Ruby’s Hash serves this role. You can use it to map names to settings, IDs to records, or symbols to method options.
user = { name: "Ada", role: "admin" }
Here, user is a Hash. Its keys are symbols, and its values are strings. Ruby also lets you inspect the type:
user.class
# => Hash
There is no built-in Dictionary class to choose instead. If a library defines a class with that name, it is a library-specific type, not Ruby’s core hash collection.
Two common hash literal forms
Ruby supports both the older hash-rocket form and label-style syntax. The first works with a wide range of key expressions; the second is a compact way to write symbol keys.
person = { "name" => "Ada" }
settings = { name: "dark" }
These hashes do not have the same key:
person["name"] # => "Ada"
settings[:name] # => "dark"
settings["name"] # => nil
The label name: creates the symbol :name. It does not create the string "name". Keep this distinction in mind when data crosses a boundary, such as when a program reads JSON with string keys and later code expects symbols.
Hash Key/Value Rules, Lookup Behavior, and Requirements
A hash stores one value for each key. Assigning a value to a key that is already present replaces the earlier value. Lookup uses hash[key]; by default, a key that is not present returns nil, though a hash can be given a default value or default procedure.
Key matching and uniqueness
Ruby checks keys using their hash and eql? behavior. In practical terms, a key must match according to those methods for a lookup to find its stored value. Similar-looking values of different types may not match.
record = { :id => 12, "id" => 13 }
record[:id] # => 12
record["id"] # => 13
record.keys.length # => 2
Because :id and "id" are different keys, both can exist in one hash. Consistent key types make code easier to reason about. If a hash uses symbol keys, use symbols when reading and updating it as well.
Assignment replaces an existing value
A hash has one current value for each matching key. Assigning again to that key updates the value; it does not add a second entry.
scores = { "Mina" => 8 }
scores["Mina"] = 10
scores
# => {"Mina"=>10}
This behavior matters when values are built in steps. A later assignment may overwrite an earlier value without warning. When that would be a mistake, check whether the key is already present with key? before writing.
Lookup, missing keys, and defaults
Square brackets are the usual way to retrieve a value:
settings = { theme: "dark" }
settings[:theme] # => "dark"
settings[:font_size] # => nil
settings.key?(:font_size) # => false
A missing key returns nil by default. That can be useful, but it also means a missing entry may be confused with a key whose stored value is actually nil. Use key? when you need to tell those cases apart.
A hash can also use a default value:
counts = Hash.new(0)
counts[:visits] += 1
counts[:visits] # => 1
counts[:errors] # => 0
A default value is returned for a missing key; it does not, by itself, add that key to the hash. A default proc can define more active behavior. Choose a default deliberately, since it changes what callers see when they request an absent key.
Syntax and behavior at a glance
| Need | Example | Result or rule |
|---|---|---|
| String key | {"name" => "Ada"} |
Key is "name" |
| Symbol key | {name: "Ada"} |
Key is :name |
| General key expression | {key => value} |
Uses the value of key |
| Read a value | hash[key] |
Returns the value, or nil by default if absent |
| Check for a key | hash.key?(key) |
Returns true or false |
| Replace a value | hash[key] = value |
Updates that key’s value |
| Preserve insertion order | hash.each { |k, v| ... } |
Visits entries in insertion order |
Implementation Practice, Testing, and Common Edge Cases
A reliable hash implementation starts with a small specification: record the Ruby version, expected key types, missing-key policy, and whether iteration order matters. Then test those behaviors directly. This avoids relying on assumptions that may not match the data your program receives.
Decide on key types before coding
Use label-style syntax for conventional symbol-keyed records:
profile = { name: "Ada", active: true }
profile[:name] = "Ada Lovelace"
Use hash rockets when keys are strings, come from variables, or need mixed types:
field = "name"
profile = { field => "Ada" }
profile["active"] = true
The choice is about key representation, not a different kind of collection. When input arrives from an external source, inspect its keys instead of assuming they have been converted. For example, a hash with "name" will not answer a lookup for :name.
Write focused behavior checks
A small test can confirm the details that matter to your application:
data = { name: "Ada" }
raise unless data[:name] == "Ada"
raise unless data.key?(:name)
raise unless data["name"].nil? data[:name] = "Grace"
raise unless data[:name] == "Grace"
If iteration order matters, test it with a sequence of inserted keys:
data = {}
data[:first] = 1
data[:second] = 2
raise unless data.keys == [:first, :second]
Ruby hashes preserve key insertion order. If your output needs a different order, sort or otherwise arrange the keys explicitly rather than relying on an accidental input order.
Avoid changing a stored key
Hash lookup depends on a key’s hash and eql? results. If a mutable object is changed while it is being used as a key, those results may change too. The stored entry can then be difficult to retrieve.
In practice, prefer stable keys such as strings, symbols, or other values that your program does not modify while they are in the hash. If a key object must change, remove the entry and insert it again after the change. For a hash with mutable keys, rehash can rebuild its internal lookup structure, but preventing the problem is usually clearer.
A practical data-handling example
Suppose an application keeps a small set of user settings. Symbol keys make the expected fields clear:
options = {
theme: "dark",
notifications: true
}
if options.key?(:theme)
puts options[:theme]
end
If the same fields come in as string keys, preserve that fact or convert the keys in a deliberate step. Do not mix :theme and "theme" without a reason: Ruby treats them as separate entries. The practical test is to confirm the actual key type at the point where the data enters your code.
Practical Applications and Data Handling Decisions
Hashes are useful when code needs to retrieve a value by a meaningful key rather than by position. The best syntax depends on the source and meaning of the keys. Choose a consistent representation, define how missing values behave, and keep ordering requirements explicit.
| Scenario | Suggested approach | Check before relying on it |
|---|---|---|
| Record-like internal data | Symbol keys, such as { status: "ready" } |
Read and update with :status |
| Text keys from input | String keys, such as { "status" => "ready" } |
Look up with "status" |
| Keys stored in variables | Hash-rocket form, such as { field => value } |
Confirm the variable holds the intended key |
| Count or accumulate values | A chosen default, such as Hash.new(0) |
Remember that a default does not add a missing key |
| Ordered output | Iterate or sort deliberately | Hash iteration follows insertion order; sorting is separate |
| Custom mutable key objects | Prefer stable keys | Changes to hash or eql? can affect lookup |
A simple decision sequence
Before implementing a hash, answer four questions:
- What Ruby version does the project declare?
- Are keys symbols, strings, or another type?
- Is a missing-key lookup valid, or should the code detect it with
key?? - Does output need insertion order, or should it be sorted by a chosen rule?
Then build a representative hash and test construction, lookup, replacement, missing-key behavior, and iteration. These checks cover the main differences that cause confusion when a programmer expects “dictionary” to mean the same thing across languages.
Conclusion
Ruby’s Hash is its built-in dictionary-style collection. The key distinctions are syntax and key type: {name: value} creates a symbol key, while {"name" => value} creates a string key. Existing keys are overwritten on assignment, missing lookups return nil by default, and hashes preserve insertion order. Decide on key types and missing-value behavior, then test them.
FAQ
Does Ruby have a Dictionary class?
No. Ruby’s built-in key-value collection is Hash. “Dictionary” is a general term for the data-structure role, not a separate core Ruby class.
Does {name: "Ada"} create a string key?
No. It creates a symbol key, :name. Use {"name" => "Ada"} to create a string key.
Are :id and "id" the same hash key?
No. A symbol and a string are distinct keys, so a hash can store a separate value for each.
What does a missing hash key return?
By default, hash[key] returns nil when the key is absent. A hash can have a default value or a default proc that changes this behavior.
How can I check whether a key exists?
Use hash.key?(key). It returns true when that key is present and false when it is not.
Does assigning to an existing key add another entry?
No. It replaces the value for that key. A hash holds one current value for each matching key.
Are Ruby hashes ordered?
Yes. Ruby hashes preserve key insertion order during iteration. Sort the keys explicitly if you need a different order.
Can a hash contain both symbol and string keys with the same text?
Yes. For example, :name and "name" are distinct, so both can appear in one hash.
Why can a mutable object be a risky hash key?
Lookup relies on the key’s hash and eql? behavior. If changing the object changes those results while it is stored, the entry may become hard to retrieve. Prefer stable keys.
Which syntax should I choose?
Use label-style syntax for conventional symbol keys. Use {key => value} when keys are strings, come from expressions, or need another type. Keep key types consistent with later lookups.
(This article was written by one of our staff writers, Andrew Kensington. Visit our Meet the Team page.)
