How to join strings in Elixir?

If you just want to join some arbitrary list:

"StringA" <> " " <> "StringB"

or just use string interpolation:

 "#{a} #{b}"

If your list size is arbitrary:

Enum.join(["StringA", "StringB"], " ")

... all of the solutions above will return

"StringA StringB"

If what you have is an arbitrary list, then you can use Enum.join, but if it's for just two or three, explicit string concatenation should be easier to read

"StringA" <> " " <> "StringB"

However, often you don't need to have it as a single string in memory if you're going to output it through e.g. the network. In that case, it can be advantageous to use an iolist (a specific type of a deep list), which saves you from copying data. For example,

iex(1)> IO.puts(["StringA", " ", "StringB"])
StringA StringB
:ok

Since you would have those strings as variables somewhere, by using a deep list, you avoid allocating a whole new string just to output it elsewhere. Many functions in elixir/erlang understand iolists, so you often wouldn't need to do the extra work.


Answering for completeness, you can also use String interpolation:

iex(1)> [a, b] = ["StringA", "StringB"]
iex(2)> "#{a} #{b}"
"StringA StringB"

There are a number of methods, but knowing how it handles nil values can determine which method you should choose.

This will throw an error

iex(4)> "my name is " <> "adam"
"my name is adam"

iex(1)> "my name is " <> nil
** (ArgumentError) expected binary argument in <> operator but got: nil
    (elixir) lib/kernel.ex:1767: Kernel.wrap_concatenation/3
    (elixir) lib/kernel.ex:1758: Kernel.extract_concatenations/2
    (elixir) lib/kernel.ex:1754: Kernel.extract_concatenations/2
    (elixir) expanding macro: Kernel.<>/2
    iex:1: (file)

This will just insert a blank "" string:

iex(1)> "my name is #{nil}"
"my name is "

As will this:

iex(3)> Enum.join(["my name is", nil], " ")
"my name is "

Also consider types. With <> you don't get any free casting:

iex(5)> "my name is " <> 1
** (ArgumentError) expected binary argument in <> operator but got: 1
    (elixir) lib/kernel.ex:1767: Kernel.wrap_concatenation/3
    (elixir) lib/kernel.ex:1758: Kernel.extract_concatenations/2
    (elixir) lib/kernel.ex:1754: Kernel.extract_concatenations/2
    (elixir) expanding macro: Kernel.<>/2
    iex:5: (file)

iex(5)> "my name is #{1}"
"my name is 1"

iex(7)> Enum.join(["my name is", 1], " ")
"my name is 1"

Performance in practice seems roughly the same:

iex(22)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is " <> "adam" end) end)
{8023855, :ok}
iex(23)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is " <> "adam" end) end)
{8528052, :ok}
iex(24)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is " <> "adam" end) end)
{7778532, :ok}
iex(25)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is #{"adam"}" end) end)
{7620582, :ok}
iex(26)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is #{"adam"}" end) end)
{7782710, :ok}
iex(27)> :timer.tc(fn -> Enum.each(1..10_000_000, fn _ -> "my name is #{"adam"}" end) end)
{7743727, :ok}

So, really depends on if you want to crash or not when the interpolated values are nil or the wrong type.