Apr 03, 2020

blog

String concatenation in Ruby

Concatenating strings in Ruby is very easy. We know that Ruby is very developer-friendly, it offers multiple ways to do so. But which one is preferred? Which is more performance efficient? In this article, we'll see different ways to concatenate a string and help you choose the best one.

String concatenation in Ruby

Ways of concatenating a string in ruby

As stated earlier, there are many ways to concatenate a string. Let's check them one by one by taking an example

Let's say our main string is 'Hello' and substring to concatenate is ' Ruby!'

So let's define variables and assume we're initiating them before trying each of the methods.

main_string = 'Hello'
substring = ' Ruby!'

1. Using concat method

> main_string.concat(substring)
=> "Hello Ruby!"

# let's print main_string.
> main_string
=> "Hello Ruby!"

2. Using plus i.e. '+' operator

> main_string += substring
=> "Hello Ruby!"

> main_string
=> "Hello Ruby!"

3. Using '<<' operator

> main_string << substring
=> "Hello Ruby!"

> main_string
=> "Hello Ruby!"

4. Using 'insert' method

> main_string.insert(-1, substring)
# -1 is the index which means at the end of string
=> "Hello Ruby!"

> main_string
=> "Hello Ruby!"

5. Ruby string Interpolation

> main_string = "#{main_string}#{substring}"
=> "Hello Ruby!"

NOTE: Kindly note that string Interpolation works when you use double quotes i.e. "" It won't work with single quotes. for e.g. '#{mainstring}#{substring}' won't work, instead it will return "#{mainstring}#{substring}".

6. Array Join method

Though it's not the method on the string directly, it is one of the ways to it.

> main_string = [main_string, substring].join
=> "Hello Ruby!"

Efficient string concatenation in Ruby

So we saw different ways to concatenate strings, you might have decided to use which you liked but hold on let's first do some benchmarking to check performance. We'll do benchmarking in two phases 1. To check time efficiency 2. To check memory efficiency, I'm using Ruby 2.6.3 for benchmarking.

require 'benchmark'
require 'benchmark/ips'
require 'benchmark/memory'

N_NUMBER_OF = 100_000
substring = ' Ruby!'

Benchmark.ips do |x|
  x.report("concat") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string.concat(substring) }
  end
  x.report("plus +") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string += substring }
  end
  x.report("append <<") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string << substring }
  end
  x.report("insert ") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string.insert(-1, substring) }
  end
  x.report("interpolate") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string = "#{main_string}#{substring}" }
  end
  x.report("Array Join") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string = [main_string, substring].join }
  end
  x.compare!
end


When N_NUMBER_OF = 100_000
Comparison:
          append <<:       72.7 i/s
             insert :       66.5 i/s - 1.09x  (± 0.00) slower
              concat:       34.5 i/s - 2.11x  (± 0.00) slower
         interpolate:        0.1 i/s - 1240.56x  (± 0.00) slower
              plus +:        0.1 i/s - 1360.10x  (± 0.00) slower
          Array Join:        0.1 i/s - 1365.94x  (± 0.00) slower

When N_NUMBER_OF = 10000
Comparison:
           append <<:      780.8 i/s
             insert :      734.1 i/s - same-ish: difference falls within error
              concat:      667.2 i/s - 1.17x  (± 0.00) slower
         interpolate:        5.2 i/s - 149.01x  (± 0.00) slower
              plus +:        5.2 i/s - 149.44x  (± 0.00) slower
          Array Join:        5.1 i/s - 151.87x  (± 0.00) slower

When N_NUMBER_OF = 1000
Comparison:
           append <<:     7411.9 i/s
             insert :     6788.0 i/s - 1.09x  (± 0.00) slower
              concat:     6238.8 i/s - 1.19x  (± 0.00) slower
              plus +:      929.4 i/s - 7.97x  (± 0.00) slower
         interpolate:      821.2 i/s - 9.03x  (± 0.00) slower
          Array Join:      800.2 i/s - 9.26x  (± 0.00) slower

The performance of '<<' is fastest amongst all, insert is quite closer to it. If you increase the iterations, you'll find that for 'interpolate' performs better than 'plus +'

N_NUMBER_OF = 10000
substring = ' Ruby!'

Benchmark.memory do |x|
  x.report("concat") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string.concat(substring) }
  end
  x.report("plus +") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string += substring }
  end
  x.report("append <<") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string << substring }
  end
  x.report("insert ") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string.insert(-1, substring) }
  end
  x.report("interpolate") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string = "#{main_string}#{substring}" }
  end
  x.report("Array Join") do
    main_string = 'Hello'
    N_NUMBER_OF.times { main_string = [main_string, substring].join }
  end
  x.compare!
end

Comparison:
              concat:      98344 allocated
           append <<:      98344 allocated - same
             insert :      98344 allocated - same
              plus +:  300489986 allocated - 3055.50x more
         interpolate:  300500576 allocated - 3055.61x more
          Array Join:  300901200 allocated - 3059.68x more

Concat, ‘<<’ and insert doesn’t create new objects and it is straightforward that interpolation and Array's join was creating new objects but '+' is also creating new objects every time, that's why it's not performance efficient.

Conclusion:

Considering the overall benchmark comparison, It's clear that '<<' performs better amongst all, so it should be preferred when performance matters, otherwise it won't make much of the difference which one you use.