100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Ruby

Hashes in Ruby

How Ruby's Hash class stores key-value pairs, including modern literal syntax, symbol keys, default values, and the Enumerable methods it inherits.

Strings, Arrays & HashesBeginner10 min readJul 9, 2026
Analogies

Hashes in Ruby

A Hash is Ruby's associative array — an insertion-ordered collection of key-value pairs, where any object can serve as a key as long as it implements hash and eql? consistently. In practice, symbols are the overwhelmingly common key type because they're immutable and memory-efficient, and modern Ruby offers a shorthand syntax specifically for symbol keys. Since Ruby 1.9, hashes preserve insertion order when iterated, which makes them reliable for building ordered reports or preserving the order fields were added.

🏏

Cricket analogy: A scorecard mapping each player's name to their runs scored is an insertion-ordered Hash, and just as a scorer relies on a consistent way to identify each player (like a jersey number) as a key, Ruby hashes need consistent hash and eql? on keys.

Literal syntax and safe access

The classic hash-rocket syntax { :key => value } works with any key type, but when keys are symbols, Ruby 1.9+ allows the more concise { key: value } form — the two are equivalent, but the shorthand cannot be used for non-symbol keys such as strings. Symbol keys and string keys are never interchangeable: { name: "Ada" }["name"] returns nil, because :name and "name" are different objects. By default hash[missing_key] returns nil, but fetch raises a KeyError on a missing key unless you supply an explicit default, making missing-key bugs loud rather than silent.

🏏

Cricket analogy: Writing { :captain => "Root" } works for any key, but the shorthand { captain: "Root" } only applies to symbol keys; a scorecard keyed by symbol :captain and one keyed by string "captain" are treated as entirely different entries, and fetch raises loudly on a missing key instead of silently returning nil.

ruby
user = { name: "Ada", role: :admin }
user[:name]      # => "Ada"
user["name"]     # => nil, string and symbol keys are distinct

user.fetch(:email)                  # raises KeyError
user.fetch(:email, "unknown")        # => "unknown", with a default

Default values, dig, and transforming hashes

Hash.new(default) lets you set a fallback value returned for missing keys instead of nil — useful for counters — and Hash.new { |hash, key| ... } goes further, computing a default on demand. For deeply nested hashes, dig safely traverses multiple levels and returns nil instead of raising if any intermediate key is missing. Because Hash includes Enumerable, transform_values reshapes every value while keeping keys unchanged, and merge combines two hashes, with a block available to resolve key conflicts explicitly.

🏏

Cricket analogy: Hash.new(0) gives a wicket-count tallier a default of zero for any bowler not yet seen, dig safely traverses a nested { team: { squad: { player: stats } } } structure returning nil if a level is missing, and transform_values could convert every player's raw score into a strike rate.

ruby
word_counts = Hash.new(0)
"the fox the lazy fox".split.each { |w| word_counts[w] += 1 }
word_counts # => {"the"=>2, "fox"=>2, "lazy"=>1}

config = { database: { host: "localhost", port: 5432 } }
config.dig(:database, :port)           # => 5432
config.dig(:database, :missing, :deep)  # => nil, no NoMethodError

prices = { apple: 1.5, banana: 0.5 }
prices.transform_values { |p| (p * 0.9).round(2) } # => {apple: 1.35, banana: 0.45}

Python's dict and Ruby's Hash both preserve insertion order since around the same era (Python 3.7 made it official; Ruby has done so since 1.9), but Ruby's Hash gains the entire Enumerable toolkit for free — the same map/select/reduce vocabulary used on arrays works identically on hashes.

Symbol keys and string keys are never interchangeable, which trips up developers parsing JSON: JSON.parse returns string keys by default, so a hash built from parsed JSON must be accessed with ["name"], not [:name], unless you call JSON.parse(json, symbolize_names: true).

  • Hashes store key-value pairs and preserve insertion order since Ruby 1.9+.
  • The key: value shorthand only works for symbol keys; hash-rocket => syntax is required for other key types.
  • fetch raises KeyError on a missing key (or accepts a default), unlike [] which silently returns nil.
  • Hash.new(default) or Hash.new { |h, k| ... } supplies a fallback for missing keys, useful for counters.
  • dig safely traverses nested hashes/arrays, returning nil instead of raising if an intermediate key is absent.
  • Symbol keys and string keys are distinct — JSON-parsed hashes use string keys by default unless symbolized explicitly.

Practice what you learned

Was this page helpful?

Topics covered

#Ruby#RubyStudyNotes#Programming#HashesInRuby#Hashes#Literal#Syntax#Safe#StudyNotes#SkillVeris