Ruby: can't add a new key into hash during iteration

Take a look at how to solve an issue with adding a key during the iteration in Ruby.
Published 2023-04-07 3 min read
Ruby: can't add a new key into hash during iteration

Ok, you got RuntimeError: can't add a new key into hash during iteration. Let’s go through why it occurs and what to do about it.

This is a common error that occurs when… you modify a hash during the iteration by adding a new key. You can encounter it in any iteration method, each, each_key, etc.

Example code that would trigger this error

our_hash.each do |k,v|

  our_hash[:new_key] = :new_value
end

 

Why this error exists?

Adding an element during the iteration would mean that the whole process is incomplete. There are two hypothetical approaches that Ruby could handle - go only through original keys or somehow add new keys to the process that easily could end up in an infinite loop. Both options are bad, therefore this error.

 

What to do about this error?

Option 1: Bad in most cases, but it works - dup

Some sources suggest duplicating the original hash like this:

our_hash.dup.each do |k,v|

  our_hash[:new_key] = :new_value
end

Since the two hashes are different entities it won’t raise an error. But in most cases, this is a bad solution. The need for adding a key during the iteration is usually a side-effect of a bad algorithm.

Option 2: Good solution in some cases - transform_keys

Sometimes this error is a result of modifying hash key naming. Thankfully Ruby has a method to do this called transform_keys. There are also transform_keys!, transform_values and transform_values! - take a look at Ruby docs for details.

Example

our_hash = {a: 1, b: 2, c: 3}
our_hash.transform_keys do |k|
  "#{k}-sufix"
end
#{"a-sufix"=>1, "b-sufix"=>2, "c-sufix"=>3}

Option 3: Good solution in some cases - collect new keys first and merge

Let’s assume you want to add some keys based on some logic. Nothing stops you from running this logic first and then merging the results into the original hash.

Example

our_hash = {a: 1, b: 2, c: 3, aa: 4}
keys_length_info = our_hash
  .keys
  .map(&:length}
  .group_by(&:itself)
  .transform_keys { |k| "#{k}-letter" }
  .transform_values(&:length)

our_hash.merge!(keys_length_info)
#{:a=>1, :b=>2, :c=>3, :aa=>4, "1-letter"=>3, "2-letter"=>1}

This example is totally made up and doesn’t make much sense in the real world, but hope you get the intention.

There are many more options and techniques since Ruby’s standard library is so impressive. You really don’t have to constrain yourself to basic iteration methods. A good place to start is by asking what goal you really want to achieve and coming up with a few ways to get there.

#ruby