How do Go channels compare to mailboxes in Erlang/Elixir?

Consider the Erlang code below, case_a, case_b, and case_c are equivalent to three channels in a for-select loop in Go.

  {case_a, ...} -> ...
  {case_b, ...} -> ...
  {case_c, ...} -> ...

But there are two possible sources of incorrectness in this Erlang code:

  • we don’t know the type of the data associated with case_a, which means we may send wrong data to this process or process the data incorrectly.
  • case_a may never happen, i.e., no code sends {case_a, ...} to this process.

I like Go channels because they have better correctness guarantee compared to mailboxes in Erlang:

  • Go channels are protected by the type checker. So programmers get alerted instantly when they try to send an int to a chan bool.
  • Go channels have to be introduced before use, e.g., via function parameters. In Erlang, I can try to receive a message in receive that nobody ever sends, but I can only receive from channels that are in scope.

I don’t have much experience in both Go and Erlang, so I’d like to know your thoughts.

One of the biggest factors is the ability to send across nodes in a cluster.

In that same vein, you can’t guarantee correctness on the other node of the cluster if updates have been deployed, for example. Similar to making an API call to an outside service that can change. There’s only so much the type checker can actually do.


That’s true. API definition languages like Protobuf are a remedy for this case. I can’t think of a better approach.

For the single node scenario, I think typed channels improve code correctness and readability.

This is the same argument oriented around static types and there are prices to pay in flexibility when introducing such a thing, moreover in elixir/erlang world we do not use low-level constructs on application layer, we use abstractions like GenServer.

Comparing erlang messages to golang channels is like comparing apples to oranges, both in terms of features and in terms on how concurrency is handled with these constructs.

This is very questionable, as things are nowadays, writing code with goroutines and channels is times more error-prone, can have deadlocks, and contains more boilerplate compared to elixir, but at the same time golang aims to be more performant by sacrificing a lot of abstraction.


I don’t really know anything about Go but there is Ergo that was announcement relatively recently that brings Erlang-style abstractions to Go.

As I understand it from having very shallow discussions with Go devs and some people around here, goroutines are kinda like “fire and hope for the best” compared to OTP’s fine-grained control and observability over processes. I mainly wanted to share the ergo project so someone please correct or expand if I’m wrong :v:

1 Like

This is a purely theoretical danger only. Unless your team is huge then nobody will attempt to use the code incorrectly or send the wrong shape of data.

Such adversarial conditions simply don’t happen in smaller teams (and I’ve never stumbled upon a big Elixir team and I know that even if it exists it is 99% likely it’s not a productive one).

That’s why you can and should put an after block.

Sure. You can also send a value to a closed channel and panic (read: abrupt abort) the program.

I love Golang but it has nasty foot-guns.

This can go both ways. It can be positive because there are many cases when you want to read from several channels – Golang offers you idioms for this (verbose but still readable enough). It can also be a negative because if you want something like an Erlang process (i.e. an actor) you would prefer the goroutine and its channel to be firmly bound to each other – but they aren’t so you end up emulating Erlang anyway (though don’t get me wrong, this is easy enough to do in most cases).

Basically, if you need more flexibility, machine speed and are ready to sacrifice correctness for speed of development – Golang is close to perfect.

If you prefer a more deliberate development with good speed and not a hard requirement to be uber-fast: just use Phoenix.PubSub with Elixir and you’ll be just fine.


I noticed another difference between message passing in Go (via channels) and in Erlang. Sending to a Go channel is a blocking operation — the sender process/goroutine blocks until another goroutine receives from the channel. Whereas in Erlang, sending a message is non-blocking.

I feel non-blocking send is better, but don’t have good arguments. I’d love to know your thoughts on this.

I don’t know Go but according to what you say it looks like this blocking mechanism prevents to flood the receiver with too much message, providing “natural” backpressure.

Process mailboxes are easily flooded and bottlenecks can arise. But it is generally not a problem because we can emulate the blocking mechanism, the most common way being “send and await response”, just as in But we do have a choice, so more flexibility and more power. The power/responsibility quote would fit well here.

1 Like

It depends.

This will block.

ch := make(chan int)
ch <- 111 // This will block because the channel is unbuffered (infinite capacity)

This will not.

ch := make(chan int, 128)
// This will NOT block unless the channel has 128 elements sent
// and nobody has requested them yet
ch <- 111

You can also use Golang’s select mechanism to make sure you will never block even if a buffered channel is full: Channel push non blocking in GoLang - DEV Community

1 Like

Yeah, but buffered channels still block when the buffer is full.

As for select, it postpones but doesn’t solve the problem — you need to send to the channel at the end of the day.

Sure, but select also gives you a mechanism for retrying and timing out. Code gets little verbose but it works fine.

1 Like