Hash#merge
hash.merge(*others) -> hash The merge method combines one map with one or more others, returning a brand new collection. The original stays untouched. When the same key appears in multiple arguments, the value from the one furthest to the right wins. You can also pass a block to control exactly what happens during conflicts.
Signature
hash.merge(*others) -> new_hash
hash.merge(*others) { |key, old_value, new_value| block } -> new_hash
Parameters
| Parameter | Type | Description |
|---|---|---|
*others | Hash | One or more hashes to merge into the receiver. Each argument must be a Hash. |
Return Value
Returns a new Hash containing all key-value pairs from self and every argument. The original is never modified. If no arguments are given, merge returns a shallow copy.
Basic Usage
defaults = { theme: "dark", locale: "en", debug: false }
user_settings = { theme: "light", debug: true }
config = defaults.merge(user_settings)
config # => {:theme=>"light", :locale=>"en", :debug=>true}
defaults # => {:theme=>"dark", :locale=>"en", :debug=>false} # unchanged
The original hashes are left alone, so you can reuse them. When you want to combine settings from multiple sources while keeping each source intact, merge gives you a clean copy without mutating any of the input hashes. This pattern is especially common when layering configuration, such as applying user preferences on top of system defaults.
Merging multiple hashes
You can pass more than one map. They are merged left to right, meaning each subsequent argument overwrites values from the previous ones. Passing multiple arguments at once is more concise than chaining individual merge calls, and it avoids constructing intermediate objects that would immediately be discarded. The order matters: the rightmost argument has the final say for any key that appears more than once.
base = { a: 1 }
h1 = { b: 2, a: 10 }
h2 = { c: 3, a: 100 }
base.merge(h1, h2)
# => {:a=>100, :b=>2, :c=>3}
Block form (conflict resolution)
When duplicate keys exist, you can give a block to decide the outcome yourself. The block receives three arguments: the conflicting key, the value from self, and the value from the hash being merged in. This callback fires once per conflicting key, making it easy to sum numbers, pick larger values, or combine entries in any way your application needs.
prices = { apple: 1.5, banana: 0.75, cherry: 2.0 }
discounts = { apple: 0.25, banana: 0.10 }
final = prices.merge(discounts) { |key, price, discount| price - discount }
final # => {:apple=>1.25, :banana=>0.65, :cherry=>2.0}
The block only runs for keys that appear in both collections. If a key is unique to one side, the block does not fire for it. This design means the block handles only genuine conflicts, which keeps the callback from firing unnecessarily on keys that simply pass through without any collision. The three block parameters give you full access to both the old and new values so you can decide which one to keep, combine them, or produce something entirely different.
Common conflict-resolution patterns:
h1 = { a: 1, b: 2 }
h2 = { a: 10, b: 20 }
# Keep the higher value
h1.merge(h2) { |_k, v1, v2| v1 > v2 ? v1 : v2 }
# => {:a=>10, :b=>20}
# Add numeric values together
h1.merge(h2) { |_k, v1, v2| v1 + v2 }
# => {:a=>11, :b=>22}
# Collect into an array
h1.merge(h2) { |_k, v1, v2| [v1, v2] }
# => {:a=>[1, 10], :b=>[2, 20]}
merge vs merge! vs update
Ruby gives you three ways to merge:
| Method | Effect | Returns |
|---|---|---|
merge | Creates a new hash | New hash |
merge! | Modifies self in place | self |
update | Alias for merge! | self |
merge! and update are identical — they share the same implementation under the hood. Use whichever name reads better in context.
config = { host: "localhost", port: 3000 }
# Non-destructive: original unchanged
new_config = config.merge(port: 8080)
config # => {:host=>"localhost", :port=>3000}
new_config # => {:host=>"localhost", :port=>8080}
# Destructive: original modified
config.merge!(port: 8080)
config # => {:host=>"localhost", :port=>8080}
Use merge when you want to preserve data. Use merge! or update when you intentionally want to mutate the original. This distinction matters most in functional pipelines and multi-step operations where an intermediate map should not be permanently changed. Prefer the non-destructive form as the default, and only reach for the bang variant when mutation is the whole point of that step.
No arguments form
Calling merge with no arguments returns a shallow copy of the original structure:
original = { a: 1, b: 2 }
copy = original.merge
copy # => {:a=>1, :b=>2}
copy.object_id == original.object_id # => false
This is equivalent to original.dup, but reads more clearly in context. Because the duplicate is shallow, nested objects inside the structure are still shared between the original and the copy. Modifying a nested array or inner map in the duplicate will also affect the original, so this technique is safe only for flat key-value structures where the values are immutable or you do not plan to mutate them.
Common Mistakes
Nested maps are not deep-merged. A one-level shallow merge is all you get. The inner structure is replaced entirely:
h = { a: { b: 1 } }
h.merge(a: { b: 2 })
# => {:a=>{:b=>2}} -- the inner hash is replaced, not merged
Without a block, later values win silently. Ruby does not warn you about overwrites. If you need to detect conflicts, use the block form.
The block is ignored when there are no duplicate keys. This is fine in most cases, but can cause confusion if your block has side effects that you expect to run.
See Also
Hash#fetch— Retrieve a value with a default or blockHash#select— Keep only selected key-value pairsHash#reject— Remove entries matching a condition