Hash#to_h
Hash#to_h converts the hash to a plain Hash instance. When called without a block, it returns self for a plain Hash but returns a new Hash for subclasses. With a block, it transforms each key-value pair into whatever you return from the block.
That makes the method useful in two different directions: normalizing a subclass back to a regular hash, or reshaping pairs while keeping the work in a single pass. In both cases, the caller gets a plain hash object at the end.
No block: regular hash
For a plain Hash, to_h returns self:
h = { a: 1, b: 2 }
h.to_h
# => { a: 1, b: 2 }
h.to_h.object_id == h.object_id # true — same object
This isn’t useful for plain hashes, but matters when dealing with subclassed hashes.
For a plain hash, the method is basically a no-op, which is why the object identity stays the same. The important part is that the behavior is still explicit, so call sites can use to_h without caring whether the object started as a subclass.
Subclassed hash
If you subclass Hash, to_h returns a plain Hash with the same content:
class CustomHash < Hash
end
ch = CustomHash.new
ch[:a] = 1
ch[:b] = 2
plain = ch.to_h
plain.class # => Hash (not CustomHash)
plain # => { a: 1, b: 2 }
This is the correct behavior for converting subclassed hashes to regular hashes before passing them to methods that expect plain Hash.
It is also the safest way to strip away subclass-specific behavior when a plain hash is the only thing the next method should see. That helps keep the boundary between custom behavior and standard data structures clear. Stripping subclass-specific behavior at the boundary is especially useful when passing data to a library or framework that expects a standard Hash and would break on a custom subclass.
With a block: transforming pairs
When you pass a block, it gets called with each key-value pair and expects a 2-element array return value:
h = { foo: 0, bar: 1, baz: 2 }
h.to_h { |key, value| [value, key] }
# => { 0 => :foo, 1 => :bar, 2 => :baz }
The block returns [new_key, new_value]. Those pairs become the new hash. This is how you transform both keys and values in one pass.
The block form is handy when the source data already arrives as key-value pairs, but the final shape needs to be different. It avoids a separate map-then-convert step and keeps the transformation readable. The block receives both the key and the value for each entry, so you can reshape the pair however you need without reaching for an intermediate collection. That single-pass design is often easier to follow than chaining several Enumerable calls, especially when the transformation touches both the key and the value in a single step.
Practical use cases
Swapping keys and values
{ a: 1, b: 2 }.to_h { |k, v| [v, k] }
# => { 1 => :a, 2 => :b }
Swapping is the simplest way to show the block form because the result is easy to verify visually. It also makes it obvious that to_h is building a brand new hash from the block output — the original hash stays untouched, and a new plain Hash holds the inverted mapping. That combination of immutability and transformation is one of the reasons the block form reads so cleanly in practice.
Filtering and transforming
scores = { alice: 95, bob: 82, carol: 98 }
# Keep only keys that are symbols, and double the values
scores.to_h { |k, v| [k, v * 2] if k.is_a?(Symbol) }
# => { alice: 190, bob: 164, carol: 196 }
Since the block must return a 2-element array, returning nil drops that pair. This filtering behavior means you can combine selection and transformation in one compact expression instead of splitting the work across separate steps. The block itself handles both the keep-or-drop decision and any value reshaping, so the surrounding code stays focused on the data:
h = { a: 1, b: 2, c: 3 }
h.to_h { |k, v| v.even? ? [k, v] : nil }
# => { b: 2 }
Returning nil is a compact way to drop unwanted pairs while keeping the rest of the conversion in the same pass. That pattern is often cleaner than building an intermediate array and pruning it afterward. The approach works well when the filtering rule is simple enough to express in the same block that handles the transformation, so the reader sees both the conversion and the selection logic in one place.
Converting from enumerable operations
Many Enumerable methods return arrays of arrays, not hashes. to_h converts them:
# group_by returns { group_label => [[k,v], [k,v]] }
users = { alice: 28, bob: 35, carol: 28 }
grouped = users.group_by { |_k, v| v }
# Each group is [[key, value], [key, value]] — not a hash
grouped.transform_values { |pairs| pairs.to_h }
# => { 28 => { alice: 28, carol: 28 }, 35 => { bob: 35 } }
This is the sort of step where to_h fits naturally after another Enumerable method. The pair arrays already exist, so the conversion just makes the nested data easier to work with. Because to_h expects pairs already, the output of methods like group_by is already in the right shape — you are just repackaging it into a proper hash structure.
Merging with transform
h1 = { a: 1, b: 2 }
h2 = { b: 3, c: 4 }
# Merge and transform in one pass
h1.merge(h2).to_h { |k, v| [k, v * 10] }
# => { a: 10, b: 30, c: 40 }
Combining merge and transform keeps the data flow short. The reader can see the merge result first, then the final shaping step right after it. Chaining merge and the block form of to_h also avoids an extra assignment, since the merge result feeds directly into the transformation without an intermediate variable.
Converting other collections
to_h also works on arrays of pairs:
[[:a, 1], [:b, 2]].to_h
# => { a: 1, b: 2 }
This is Enumerable#to_h, which Hash includes. The result is a plain Hash.
That shared behavior is useful because it means the method can normalize multiple collection shapes without requiring a different API for each one. The end result is still a regular hash.
Keyword arguments
Ruby 3 dropped the auto-conversion of keyword argument hashes. to_h is useful for explicitly converting when you need to pass a hash as keyword arguments:
def greet(name:, greeting: "Hello")
puts "#{greeting}, #{name}!"
end
opts = { name: "Alice", greeting: "Hi" }
greet(**opts) # works in Ruby 3
greet(**opts.to_h) # explicit conversion (redundant but clear)
The keyword-argument example is mostly about clarity. Even when the conversion is redundant, it shows exactly where the hash shape matters, which can be helpful in code that has to stay obvious to the next reader.
Empty hash
Calling to_h on an empty hash returns an empty hash (or a new empty hash for subclasses):
{}.to_h
# => {}
CustomHash.new.to_h
# => {}
An empty hash stays empty, which is exactly what you want from a normalization method. The point is not to create new data, but to preserve the hash shape in a predictable way.
See Also
- /reference/hash-methods/to-a/ — convert a hash to an array of key-value pairs
- /reference/hash-methods/transform-keys/ — transform only keys, keeping values
- /reference/hash-methods/transform-values/ — transform only values, keeping keys