rubyguides

Hash#merge!

merge!(*other_hashes) { |key, old_value, new_value| block_result } → self

merge! is the in-place version of Hash#merge. It is useful when you want to update a collection directly and keep using the same object afterward. Because it mutates the receiver, it fits configuration updates, request handling, and other places where the original map should stay visible to the rest of the code.

Signature

merge!(*other_hashes) → self
merge!(*other_hashes) { |key, old_value, new_value| block_result } → self

Hash#merge! accepts one or more Hash arguments and merges them into self. It always returns self — the same object that was modified, not a copy.

The block is optional. When provided, it is called only for keys that exist in both collections.


Basic Usage

config = { environment: "development", port: 3000 }
overrides = { port: 8080, debug: true }

config.merge!(overrides)
config
# => { environment: "development", port: 8080, debug: true }

The values from overrides take precedence over config for duplicate keys. port was 3000 and becomes 8080. debug was absent and is added.

This is the simplest case to remember: existing keys can change, and missing keys are appended. The method does not create a new hash, so any reference that points at the original object sees the updated values immediately. That immediate visibility is the main reason to choose merge! over merge — when you need downstream code to see the changes without reassigning the variable that holds the collection, mutation is the right tool.


Resolving conflicts with a block

When a key appears in both hashes and you pass a block, the block decides the final value:

scores = { alice: 10, bob: 20 }
bonus = { alice: 5, bob: 5 }

scores.merge!(bonus) { |_key, old, new| old + new }
scores
# => { alice: 15, bob: 25 }

The block receives three arguments: key, old_value (from self), and new_value (from the hash being merged in). Its return value becomes the stored value.

That block lets you decide how much priority the incoming data should have. You can add the values together, keep the old value, choose the larger one, or apply any other rule that makes sense for your application. The important part is that the choice stays local to the merge, so the calling code does not need extra branching.

Without a block, the new value silently wins:

h1 = { a: 1, b: 2 }
h2 = { b: 3 }

h1.merge!(h2)
h1
# => { a: 1, b: 3 }

No Arguments

Calling merge! with no arguments returns self unchanged. This may seem like a no-op, but it is worth knowing because code that dynamically passes an empty or nil list of hashes still runs without an error. The method tolerates the absence of arguments gracefully, which keeps conditional merge logic simpler — you can pass *override_list without guarding for an empty splat.:

h = { a: 1, b: 2 }
h.merge!
# => { a: 1, b: 2 }

If you pass a block with no arguments, the block is silently ignored.

That behavior makes merge! easy to call in code that sometimes has overrides and sometimes does not. You can pass a list of hashes only when you have one, and the method still behaves predictably when the list is empty.


Multiple Hashes

You can merge more than one collection at once. They are processed left to right:

defaults = { timeout: 30, retries: 3 }
env_config = { timeout: 60 }
user_config = { retries: 5 }

defaults.merge!(env_config, user_config)
defaults
# => { timeout: 60, retries: 5 }

Each subsequent source overwrites keys set by the previous ones.

This left-to-right rule matters when you layer defaults, environment values, and user options. The earlier hashes set the baseline, and the later hashes refine or override those values in a fixed order. That predictable sequence means you can stack configuration sources with confidence, knowing that the last writer always wins.


The update Alias

Hash#update is the same method as Hash#merge!. Both names point to the same C implementation, so there is no behavior difference at all. The alias exists because “update” was the original name in some Ruby documentation and older codebases, while “merge!” mirrors the non-mutating merge more directly. Use whichever one fits the existing style of the project.:

h = { a: 1 }
h.update(b: 2)
h
# => { a: 1, b: 2 }

Use whichever name reads better in context.

Some codebases prefer update because it reads like a direct state change, while others prefer merge! because it matches the non-mutating merge name. Either way, the important detail is that both names mutate the same receiver.


Gotchas

Mutation risk. merge! modifies self in place. If the same Hash object is referenced by another variable, you will see those changes reflected there too:

original = { a: 1 }
copy = original
original.merge!(b: 2)

original
# => { a: 1, b: 2 }
copy
# => { a: 1, b: 2 }  # also changed

If you need a non-mutating version, see Hash#merge.

TypeError on non-Hash arguments. Passing a non-Hash object raises a TypeError. This guard protects against accidentally calling merge! with a string, integer, or any other value that does not have key-value pairs. The error message is clear enough to point you toward the source of the problem, which usually means a variable that was expected to hold a hash ended up with something else.:

h = { a: 1 }
h.merge!("not a hash")
# => TypeError: no implicit conversion of String into Hash

That type check is helpful because it prevents accidental merging of unrelated values. If you need to combine data that is not already key-value data, convert it first so the method receives the structure it expects.


See Also