Guglielmo Bartelloni

Why Elixir is my favorite programming language

Written by Guglielmo Bartelloni

Originally published on LinkedIn on December 30, 2024.

Elixir is a very cool functional programming language. In my years of programming, I’ve used several languages, mostly object-oriented, but Elixir stood out because of its conciseness and powerful concepts that make you think, “Why don’t other programming languages have these features?”

Elixir runs on the BEAM virtual machine that was built in the 80s to support the telecommunications systems that needed to be reliable and fault-tolerant. For these characteristics Elixir is used at companies like WhatsApp and Discord to handle billions of messages every day.

In this article we’ll explore why it’s my favorite language.

The syntax

Let’s start with the first thing you notice when you look at the code: the syntax. The following is a simple CSV parser.

defmodule Deliverex.PostalCode.DataParser do
  @file_path "data/gi_comuni_cap.csv"

  def parse_data do
    [_header | rows] =
      File.read!(@file_path)
      |> String.split("\n", trim: true)

    rows
    |> Stream.map(&String.split(&1, ";"))
    |> Enum.into(%{})
  end
end

A simple CSV parser written in Elixir

In the example above, there’s a lot going on, but don’t worry, I’ll explain the details. This takes a CSV file and converts it into a usable data structure.

The first line defines the module, which is a way to group related functions together. Think of it like a class in an OOP language. The @file_path line is a module attribute, which in this case is a string containing the file path. We then define a function called parse_data. Since Elixir is a functional programming language, every function returns a value and has no side effects. In this case, the return value of the function is the result of its last statement.

If you’ve worked with a programming language that has “functional-like” features, you’ll find the function body easy to understand. Notice the |> symbol—this is the pipe operator. It lets you chain function calls, meaning the output of the function before the operator becomes the first argument of the next function.

For example, File.read!(@file_path) returns the content of the file. We then take this content and split it into lines using String.split("\n", trim: true). This is equivalent to writing:

String.split(File.read!(@file_path), "\n", trim: true)

The equivalent nested function call

However, the first version is more readable than the second. This is NICE because it removes nesting!

Moreover, you can see that there are no curly braces; the start and end of a block are usually defined by a do and an end.

I’m used to seeing curly braces since I mainly work with Java. Of course, this is a personal preference, but I find this syntax to be tidier and easier on the eyes.

Pattern Matching

This is one of Elixir’s killer features. Let’s see an example:

defmodule OrderProcessor do
  def process_order(order) do
    case order do
      {:paid, item, quantity} ->
        "Processing order for #{quantity} #{item}(s)"

      {:pending, _item, _quantity} ->
        "Order is still pending payment"

      {:cancelled, item, quantity} ->
        "Order for #{item} has been cancelled"

      _ ->
        "Invalid order format"
    end
  end
end

Pattern matching in an order processor

You can see that by calling the process_order function with an order, you can control the flow by matching the content of the order. The function will return one of the four messages according to the order’s content.

But this is a feature that other programming languages have and it’s not really unique. Let’s see another example:

{item, quantity} = order
1 = quantity

The match operator can destructure and assert data

This is valid Elixir code because the = sign is not only an assignment operator; it’s called the match operator. It matches what’s on the right with what’s on the left, and if the two sides do not match, we will get a match error. It’s a powerful concept because you can explicitly “assert” the shape of the data and destructure it with ease.

[a, b, c] = [1, 2, 3]
IO.puts a
# will print 1

List destructuring with pattern matching

So cool!

Let’s see another pattern matching example:

defmodule Fibonacci do
  def fib(0) do 0 end
  def fib(1) do 1 end
  def fib(n) do fib(n - 1) + fib(n - 2) end
end

The Fibonacci sequence in Elixir

Yes, you read it right: you can declare multiple functions with the same name but different parameter values, and Elixir will call the appropriate one based on the value. For example, when fib is called with n = 0, Elixir will execute the first function; when called with n = 1, it will execute the second function; and for all other cases, the third one will be executed.

The tooling

Today, a great programming language isn’t truly great unless it offers an exceptional developer experience, supported by robust tooling.

Fortunately, Elixir has this covered with a tool which is Mix, a versatile build system for managing tasks such as compiling code, running tests, and handling dependencies and projects. Want to compile the project? mix compile. Want to test the project? mix test. There’s even a code formatter built in with mix format, and this is huge. You’ll know this if you’ve worked on large projects: there won’t be any debates about which formatting style to use because there is only one correct style, and it’s the built-in one.

Moreover, frameworks can add specific commands to Mix, like in Phoenix (Elixir’s web framework), which has generators to create the skeleton of components.

And it’s all built into the language, so there is no decision to make. Yes, I’m looking at you, Java, with Maven, Gradle, and the madness that comes with them.

The average Mix user

The Actor Model

When building concurrent and distributed systems, it’s often not a great experience with common programming languages because asynchronous operations are not typically first-class citizens. Even in languages like Go, where it’s theoretically easy to create concurrent applications, managing data between processes becomes unintuitive. This forces you to focus too much on designing the system rather than solving the problem your application is meant to address.

Introducing the Actor Model, a simple concept that powers Elixir’s concurrent system. It’s not a new model; in fact, it’s very old, but it works so well and is so easy to grasp.

Every Elixir process is an actor that doesn’t share state with anyone. The only way it can learn about the world outside is by reading messages in its mailbox and acting accordingly.

Elixir runs on the Erlang Virtual Machine (BEAM), which is designed to handle lightweight processes. By lightweight, I mean each process can consume as little as only about 3 KB of memory, an incredibly small footprint. This efficiency makes it cost-effective to create a process for nearly everything, as they are inexpensive to spawn. Moreover, these processes don’t share state with each other; from their perspective, they exist in isolation. The only way they can receive information is through messages sent to them.

Actors communicate through messages in their mailboxes

This model is very close to how we think about interactions because it is how we live in the real world: we send messages to each other, written or spoken. So it becomes easy to design concurrent applications focusing on the business logic because this abstraction is second nature to us.

This model offers another key advantage: processes can be distributed across multiple machines and still communicate seamlessly, as if they were all local. The location doesn’t matter to the processes themselves; they continue exchanging messages the same way, making horizontal scaling easy and transparent.

Another key concept in Elixir is Supervisors: processes that oversee other processes, ensuring they stay alive and automatically restarting them if they crash. This approach aligns with the “Let it crash” philosophy: when a process fails, it’s often because its state became corrupted. The best solution is to restart it with a clean state, much like restarting your computer to fix an issue. It’s the same concept at play here.

You can have a hierarchy of supervisors that each control part of your application, making it very robust and fault-tolerant. This is called a Supervision Tree:

Example supervision tree for a delivery system

Phoenix LiveView

Last but definitely not least is Elixir’s web framework, Phoenix.

The Phoenix framework deserves an entire article on its own, but I’ll focus on the feature that I find the coolest: LiveView. It’s a great concept that lets you skip writing JavaScript for reactive applications.

Phoenix Framework

You might be asking: How?! The answer is WebSockets.

For each user that connects to your app, Phoenix creates an Elixir process called a LiveView which manages the interactions of that particular user for the page.

The communication between the user and the LiveView is done through WebSockets. For example, when a user clicks a button, an event message is sent to the server process, which responds with the appropriate HTML diff, and the client then plugs it into the correct spots.

This approach ensures the state on the server is tightly coupled to the state of the client. As a result, you get all the advantages of server-side control with the reactivity typically associated with client-side frameworks. And all of this is achieved without YOU writing a single line of JavaScript.

It’s obvious that under the hood, Phoenix uses JavaScript, for example, to create the WebSocket connection, but the important thing is that you don’t have to deal with JavaScript when writing reactive features.

Cool, but isn’t creating a process that stays alive for the entire user interaction inefficient? No, because, as we mentioned, an Elixir process is very lightweight. As a result, we can handle even millions of users, and therefore millions of processes, without any problems.

That’s it, those are some of my favorite things about the Elixir programming language. I’ve skipped over many aspects, so I encourage you to explore more on your own. I’ll leave Elixir: The Documentary, which inspired me to learn the language in the first place. Feel free to leave a comment and let me know what you think!