Elixir Introduction

This chapter will teach you the absolute basics of Elixir, just enough for you to become productive with Phoenix.

Buckle up. It is going to be a bumpy and sometimes dull ride. It’s tough to teach all the needed Elixir knowledge in just one chapter.

Elixir version

All code examples are written and tested for Elixir version 1.10.2. Please make sure that you have that or a higher version installed.

$ elixir -v
Erlang/OTP 22 [erts-10.6.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Elixir 1.10.2 (compiled with Erlang/OTP 21)

Elixir’s Interactive Shell (iex)

Your Elixir installation comes with Elixir’s Interactive Shell (iex), which we will use for most of the examples in this chapter. Please go to your command line and fire it up:

$ iex
Erlang/OTP 22 [erts-10.6.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.10.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> (1)
1 This is your iex prompt.
You have to press CTRL-C twice (or CTRL-\ once) to exit iex.

The iex will be your trusted friend during the work with this book and later while working with Phoenix. While programming in development mode, you can use it for diving into the core of your Phoenix application. You can do so too while being in production mode but that is the equivalent to open-heart surgery. It can be a lifesaver, but you need to know what you are doing.

iex offers autocomplete when possible. So when in doubt press TAB.
iex offers a history too. To access the last command, just press on the arrow-up key.

Help in the iex shell

iex has a built-in help function h/1 which gives you access to some basic documentation:

iex(2)> h length/1

                                def length(list)

  @spec length(list()) :: non_neg_integer()

guard: true

Returns the length of the list.

Allowed in guard tests. Inlined by the compiler.

# Examples

    iex> length([1, 2, 3, 4, 5, 6, 7, 8, 9])

Hello world!

The classic! But never the less very important. You can use the function IO.puts() to print a string to standard output:

iex(1)> IO.puts("Hello world!")
Hello world!

You should always enclose strings within double quotes. If you use single quotes, that creates a charlist, which is a different type.

In case there are double quotes within a string you have to escape them with backslashes:

iex(2)> IO.puts("With double quotes: \"Hello world!\"")
With double quotes: "Hello world!"
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
1 Don’t be afraid of the BREAK menu. With the first Ctrl+C the iex displays this list of choices (the BREAK menu) and with the second Ctrl+C you end the iex session.

Basic Calculations

We can use the types integer (integer numbers) and float (real numbers) to do all sorts of calculations. We can use the usual operators (+, -, etc.). Here are a few examples:

$ iex
Erlang/OTP 22 [erts-10.6.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.10.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 1 + 1
iex(2)> 1.1 + 1
iex(3)> 2 - 1
iex(4)> 10 * 1000000000000000
iex(5)> 23 / 3

Logical Expressions

A type boolean can store the values true or false. These can be used with the operators and, or and not:

iex(1)> true and true
iex(2)> false and false
iex(3)> true or false
iex(4)> not true

The operators and, or and not can only work with boolean values. The operators && (and), || (or) and ! (not) do the same but are a bit more free-spirited and accept truthy and falsy values (false or nil).


You already know how variables work from experiences in other programming languages. Therefore we can dive right into it. Variable names follow the snake_case format and start with a lower case. Some examples:

iex(1)> length = 10 (1)
iex(2)> width = 23
iex(3)> area = length * width
1 We use the operator = to bind the value 10 to the variable with the name length.

If you start a variable name with a capital error you will get an error:

iex(4)> Radius = 2
** (MatchError) no match of right hand side value: 2 (1)
1 Yes, MatchError is a rather strange error message here. It will make more sense later. Binding values to variables is a bit more complicated than it seems right now.

Modules and Functions

So far, we have looked at basic calculations and types in isolation. However, if we want to create an application, we will need to combine these calculations and types in a structured way. To see how this is done, we need to look at modules and functions.

In Elixir, code is organized into modules, and each module is a collection of functions.

iex(1)> defmodule Store do (1)
...(1)>   def total_price(price, amount) do (2)
...(1)>     price * amount (3)
...(1)>   end
...(1)> end
{:module, Store,
 <<70, 79, 82, 49, 0, 0, 5, 4, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 133, 0,
   0, 0, 14, 12, 69, 108, 105, 120, 105, 114, 46, 83, 116, 111, 114, 101, 8, 95,
   95, 105, 110, 102, 111, 95, 95, 7, ...>>, {:total_price, 2}} (4)
iex(2)> Store.total_price(10,7) (5)
1 defmodule is the keyword to define a module. The name of a module starts with a capital letter.
2 def is the keyword to define a function within a module.
3 The return value of a function is the return value of the last expression in the function.
4 The return value of creating the module. To save space, we will abbreviate this output in the next examples.
5 A function of a given module can be called from outside the module with this syntax.

defmodule and def use a do …​ end construct to begin and end.

Module names use CamelCase starting with a capital letter. Function names use snake_case.

You can also define a module in a separate file (with the .exs extension), and then call the function with iex filename.exs.

As an example, save the following module to math.exs.

defmodule Math do
  def sum(x, y) do
    x + y

  def difference(x, y) do
    x - y

Then, if you run iex math.exs, you can access the functions in the Math module in iex.

iex(1)> Math.sum(1, 2)
iex(2)> Math.difference(3, 1)

Private Functions

Sometimes you want to define a function within a module without exposing it to the outside world. You can do this with a private function which gets declared with defp:

iex(1)> defmodule Area do
...(1)>   def circle(radius) do
...(1)>     pi() * radius * radius
...(1)>   end
...(1)>   defp pi do (1)
...(1)>     3.14
...(1)>   end
...(1)> end
{:module, Area, ...
iex(2)> Area.circle(10) (2)
iex(3)> Area.pi (3)
** (UndefinedFunctionError) function Area.pi/0 is undefined or private
1 The function pi/0 is a private function.
2 The function circle/1 can be called from outside the module. It can use the private function pi/0 from within the module.
3 The function pi/0 can not be called from outside the module.

Function Arity

In the last couple of sentences, you probably recognized that the names of functions were followed by the number of parameters. We refer to the pi function as pi/0 and the circle function as circle/1. We call this number arity. Arity is kind of a big thing in Elixir. Why? Because not just the function name but also the arity defines a function. For example, the Rectangle module below has two functions with the same name, but different arity, and so they are treated as different functions:

iex(1)> defmodule Rectangle do
...(1)>   def area(a) do (1)
...(1)>     a * a
...(1)>   end
...(1)>   def area(a, b) do (2)
...(1)>     a * b
...(1)>   end
...(1)> end
{:module, Rectangle, ...
iex(2)> Rectangle.area(9) (3)
iex(3)> Rectangle.area(4, 5) (4)
1 The function area/1 with an arity of 1 accepts one parameter.
2 The function area/2 with an arity of 2 accepts two parameters. This is essentially a different function from area/1.
3 So to calculate the area of a square you can call area/1 with just one parameter.
4 All non square rectangle areas have to be calculated with area/2 which accepts two parameters.

Hierarchical Modules

In a big project, you will have multiple layers of Module namespaces to keep everything in some sort of structure.

This can be done by adding . between the Module names:

iex(1)> defmodule Calculator.Area do
...(1)>   def square(a) do
...(1)>     a * a
...(1)>   end
...(1)> end
{:module, Calculator.Area, ...
iex(2)> Calculator.Area.square(5)

It is just a shortcut. You could also nest the Modules:

iex(1)> defmodule Calculator do
...(1)>   defmodule Area do
...(1)>     def square(a) do
...(1)>       a * a
...(1)>     end
...(1)>   end
...(1)> end
{:module, Calculator, ...
iex(2)> Calculator.Area.square(5)


We can import access to public functions from other modules. So that we don’t have to use their fully qualified name.

iex(1)> defmodule Rectangle do
...(1)>   def area(a) do
...(1)>     a * a
...(1)>   end
...(1)>   def area(a, b) do
...(1)>     a * b
...(1)>   end
...(1)> end
{:module, Rectangle, ...
iex(2)> import Rectangle (1)
iex(3)> area(5) (2)
1 Here we import Rectangle to have all the functions of that module at our fingertips.
2 No need to Rectangle.area/1 any more area/1 is just fine.

And you can also just import special functions from that module:

iex(3)> import Rectangle, only: [area: 2] (1)
iex(4)> area(1) (2)
** (CompileError) iex:7: undefined function area/1

iex(7)> area(1,5) (3)
1 Let’s just import area/2 but not all the other functions of that module.
2 I try to run area/1, but that triggered an error because I didn’t import it.
3 Just works fine.
Whenever you just use a given function without a module name before it, that means that the module has already been imported by Elixir (e.g. the Kernel module gets imported automatically).

Import Hierarchical Modules

Often your want to import hierachical modules. Here’s how:

iex(1)> defmodule Calculator.Area do
...(1)>   def square(a) do
...(1)>     a * a
...(1)>   end
...(1)> end
{:module, Calculator.Area, ...
iex(2)> import Calculator.Area
iex(3)> square(5)


alias sets an alias for a module.

iex(1)> defmodule Calculator.Area do
...(1)>   def square(a) do
...(1)>     a * a
...(1)>   end
...(1)> end
{:module, Calculator.Area, ...
iex(2)> alias Calculator.Area, as: Area (1)
iex(3)> Area.square(99)
iex(4)> alias Calculator.Area (2)
iex(5)> Area.square(99)
1 Set an alias for Calculator.Area as Area.
2 A shortcurt for that specific case. Same result but less to type.


use allows a module to inject code into the current module, such as importing modules, defining new functions, setting a module’s state, etc.

In many of the tests in your Phoenix application, you will see use ExUnit.Case, which performs certain checks, sets some module attributes and imports needed modules.


An atom is a constant whose name is its value. In some other programming languages, these are known as symbols. Atoms start with a :

Atoms are often used to tag values and messages. For example, functions that might fail often have the return values {:ok, value} or {:error, message}.

Atoms are also used to reference modules from Erlang libraries.

iex(1)> :red
iex(2)> :blue
iex(3)> is_atom(:blue) (1)
1 The function is_atom() can be used to check if something is an atom.
You should write atoms in snake_case or CamelCase. The usual Elixir convention is to use snake_case.


We already used a string in the Hello World example. The following examples show how strings can be used with variables:

iex(1)> first_name = "Stefan" (1)
iex(2)> last_name = "Wintermeyer"
iex(3)> name = first_name <> " " <> last_name (2)
"Stefan Wintermeyer"
iex(4)> greeting = "Hello #{first_name}!" (3)
"Hello Stefan!"
iex(5)> counter = 23
iex(6)> "Count: #{counter}" (4)
"Count: 23"
1 We assign the string "Stefan" to the variable with the name first_name.
2 The <> operator can be used to concatinate strings.
3 #{} is used to interpolate strings. It can be used to inject a variable into a string.
4 Elixir’s string interpolation also works with integers. By default, it can handle integers, floats, some lists (later more on lists) and atoms.

String Functions

The String module contains functions for working with strings. Here are some examples:

iex(1)> String.downcase("SToP SHoutING!")
"stop shouting!"
iex(2)> String.split("no fist is big enough to hide the sky") (1)
["no", "fist", "is", "big", "enough", "to", "hide", "the", "sky"]
iex(3)> String.split("mail@example.com", "@") (2)
["mail", "example.com"]
iex(4)> String.to_integer("555")
1 String.split/1 divides a string into substrings at each whitespace.
2 String.split/2 is similar to String.split/1, but it also allows you to define what pattern to use when splitting the string.
remember that you can also access the documentation for the String module in iex by running h String.

The Pipe Operator (|>)

Quite often one wants to chain a couple of different functions in a row. Let’s assume you want to reverse a string with String.reverse/1 and capitalize it with String.capitalize/1 afterwards. Here’s the code to do that:

iex(1)> String.reverse("house") (1)
iex(2)> String.capitalize("esuoh") (2)
iex(3)> String.capitalize(String.reverse("house")) (3)
1 String.reverse/1 reverses the string.
2 String.capitalize/1 capitalizes all the letters in a string.
3 Connect the two functions.

The problem with String.capitalize(String.reverse("house")) is the lack of readability. It kind of works with just two functions, but what about one or two more functions in that line? Here comes the pipe operator |> to the rescue. It is a piece of syntactic sugar. Have a look:

iex(4)> String.reverse("house") |> String.capitalize() (1)
1 The pipe operator |> passes the result of the first function to the first parameter of the following function.

Of course you can use multiple pipe operators:

iex(5)> String.reverse("house") |> String.capitalize() |> String.slice(0, 3)

By using the pipe operator, the code becomes more readable and more maintainable.

Lists and Tuples

We store multiple elements in lists and tuples. Lists and tuples look alike but are quite different performance-wise.

  • Tuples are fast when you have to access its data but slow when you want to change its data. They are stored contiguously in memory. Accessing one element of a tuple or getting the size of it is fast and always takes the same amount of time.

  • Lists are stored as linked lists in memory. One element holds it’s own value and a link to the next element. Accessing single elements and the length of lists is a linear operation which takes more time. The longer the list, the more time it takes. But it is fast to add a new element to the end of a list.

Right now, you don’t need to lose sleep over the decision of which one to use. Throughout the book, you’ll get a feeling which one is best suited for what problem.


Lists store multiple values, and they can contain different types. A list is enclosed in brackets ([]):

iex(1)> [1, 2, 3, 4]
[1, 2, 3, 4]
iex(2)> ["a", "b", "c"]
["a", "b", "c"]
iex(3)> [1, "b", true, false, :blue, "house"]
[1, "b", true, false, :blue, "house"]

The operators ++ and -- can be used to concatenate and substract lists from each other:

iex(1)> [1, 2] ++ [2, 4] (1)
[1, 2, 2, 4]
iex(2)> [1, 2] ++ [1] (2)
[1, 2, 1]
iex(3)> [1, "a", 2, false, true] -- ["a", 2] (3)
[1, false, true]
1 Makes total sense.
2 So does this.
3 A bit trickier. The second and third element of the first list get subtracted.

Head and Tail of Lists

A lot of times Elixir developers want to work with the head (the first element) and tail (the rest) of a list. The following examples show how the functions hd/1 and tl/1 can be used to return these values:

iex(1)> shopping_list = ["apple", "orange", "banana", "pineapple"] (1)
["apple", "orange", "banana", "pineapple"]
iex(2)> hd(shopping_list) (2)
iex(3)> tl(shopping_list) (3)
["orange", "banana", "pineapple"]
iex(4)> shopping_list (4)
["apple", "orange", "banana", "pineapple"]
1 We define a list and bind it to the variable shopping_list.
2 hd/1 fetches the first element of the list.
3 tl/1 fetches the rest of the list.
4 The shopping_list itself hasn’t changed.

Let’s see what happens with empty lists or lists which just have one element:

iex(6)> hd([]) (1)
** (ArgumentError) argument error
iex(6)> tl([]) (2)
** (ArgumentError) argument error
iex(6)> hd(["grapefruit"]) (3)
iex(7)> tl(["grapefruit"]) (4)
1 You can’t get the head of an empty list.
2 And there is no tail of an empty list.
3 There is a "head" of a list with one element.
4 The "tail" of a file with one element is an empty list.


The function length/1 tells how many elements a list contains:

iex(1)> shopping_list = ["apple", "orange", "banana", "pineapple"]
["apple", "orange", "banana", "pineapple"]
iex(2)> length(shopping_list)
iex(3)> length([1, 2])
iex(4)> length([])

List Functions

When working with lists, you will often use functions from the Enum module. There is also a List module, which contains a few useful list functions.

Here are a few examples:

iex(1)> numbers = [1, 5, 3, 7, 2, 3, 9, 5, 3]
[1, 5, 3, 7, 2, 3, 9, 5, 3]
iex(2)> Enum.max(numbers) (1)
iex(3)> Enum.sort(numbers) (2)
[1, 2, 3, 3, 3, 5, 5, 7, 9]
iex(4)> words = ["nothing", "like", "the", "sun"]
["nothing", "like", "the", "sun"]
iex(5)> Enum.join(words, " ")
"nothing like the sun"
iex(6)> List.last(words)
1 Enum.max/1 returns the maximum value in a list.
2 Enum.sort/1 returns a new list with the values sorted in ascending order.

We will see more examples from the Enum module when we look at higher-order functions later in this introduction.


Like lists, tuples can hold multiple elements of different types. The elements are enclosed in curly braces ({}):

iex(1)> {1, 2, 3} (1)
{1, 2, 3}
iex(2)> {:ok, "test"} (2)
{:ok, "test"}
iex(3)> {true, :apple, 234, "house", 3.14} (3)
{true, :apple, 234, "house", 3.14}
1 A tuple which contains three integers.
2 A tuple which contains one atom that represents the status and a string. It is something prevalent in Elixir. You will see this a lot.
3 A tuple with values of different types.

We can access an element of a tuple with by passing the index to the elem/2 function:

iex(1)> result = {:ok, "Lorem ipsum"}
{:ok, "Lorem ipsum"}
iex(2)> elem(result, 1) (1)
"Lorem ipsum"
iex(3)> elem(result, 0) (2)
1 The function elem/2 gives us a fast access to each element of a tuple.
2 The count starts with 0 for the first element.

Tuple Functions

The Tuple module contains functions for working with tuples. Here are some examples:

  • Tuple.append/2 adds an element to a tuple.

  • Tuple.delete_at/2 deletes an element of a tuple.

  • Tuple.insert_at/3 adds an element at a specific position.

  • Tuple.to_list/1 converts a tuple to a list.

  • Tuple.size/1 returns the number of elements of the tuple.


iex(1)> results = {:ok, "Lorem ipsum"}
{:ok, "Lorem ipsum"}
iex(2)> b = Tuple.append(results, "Test")
{:ok, "Lorem ipsum", "Test"}
iex(3)> c = Tuple.delete_at(b, 1)
{:ok, "Test"}
iex(4)> d = Tuple.insert_at(b, 1, "ipsum")
{:ok, "ipsum", "Lorem ipsum", "Test"}
iex(5)> new_list = Tuple.to_list(d)
[:ok, "ipsum", "Lorem ipsum", "Test"]
iex(6)> tuple_size(d)

Higher-Order Functions

In Elixir, functions can be used like any other variable. For example, they can be passed to other functions as parameters.

A function that takes another function as one of its parameters is called a higher-order function, and these are very commonly used in Elixir.

When passing a function to a higher-order function, we need to use an anonymous function, and that is what we will look at next.

Anonymous Functions

Anonymous functions are functions that are defined without any name.

You define anonymous functions using the fn keyword:

iex(1)> greeting = fn(name) -> "Hello #{name}!" end (1)
#Function<7.126501267/1 in :erl_eval.expr/5>
iex(2)> greeting.("Bob") (2)
"Hello Bob!"
iex(3)> greeting.("Alice")
"Hello Alice!"
iex(4)> square_area = fn a -> a * a end (3)
#Function<7.126501267/1 in :erl_eval.expr/5>
iex(5)> square_area.(10)
iex(6)> area = fn width, length -> width * length end (4)
#Function<13.126501267/2 in :erl_eval.expr/5>
iex(7)> area.(2,8)
1 We create an anonymous function and bind it to the variable greeting.
  • fn tells Elixir that you want to define a function.

  • name is a parameter we can use to inject values.

  • is the operator to indicate the following expression is the body of the function.

  • end indicates the end of the function.

2 We need to use the . (dot) operator to run anonymous functions.
3 You don’t have to surround the function arguments with parentheses. They are optional.
4 Like regular functions, anonymous functions can be called with multiple arguments. The arguments are separated by commas.

Most of the time anonymous functions are simple one liners. But they don’t have to be:

iex(1)> circular_area = fn radius ->
...(1)>   pi = 3.14159265359
...(1)>   pi * radius * radius
...(1)> end
#Function<7.126501267/1 in :erl_eval.expr/5>
iex(2)> circular_area.(3)

Let’s now look at using anonymous functions with higher-order functions:

iex(1)> numbers = [1,2,3,4,5,6,7,8,9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
iex(2)> Enum.filter(numbers, fn num -> rem(num, 2) == 0 end) (1)
[2, 4, 6, 8]
iex(6)> Enum.map(numbers, fn x -> x * x end) (2)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
1 Enum.filter/2 filters a list and returns those elements for which the function returns true. The rem/2 function calculates the remainder after integer division.
2 Enum.map/2 calls the given function for every item in the list and returns a new list.

The & operator

Another way of creating anonymous functions is to use the & operator, which is called the capture operator.

iex(1)> second = &Enum.at(&1, 1) (1)
#Function<44.97283095/1 in :erl_eval.expr/5>
iex(2)> second.([1,2,3,4]) (2)
iex(3)> is_negative? = &(&1 < 0)
#Function<44.97283095/1 in :erl_eval.expr/5>
iex(4)> is_negative?.(-1)
1 &1 refers to the first parameter.
2 Again, we need to use the . (dot) operator to run anonymous functions.

And here are examples of using the capture operator with higher-order functions.

iex(1)> maybe_numbers = [1, nil, 4, nil, 5]
[1, nil, 4, nil, 5]
iex(2)> Enum.filter(maybe_numbers, &is_integer(&1)) (1)
[1, 4, 5]
iex(3)> Enum.filter(maybe_numbers, &is_integer/1) (2)
[1, 4, 5]
iex(4)> Enum.sort([1, 2, 3], &(&1 >= &2)) (3)
[3, 2, 1]
1 &1 refers to the first parameter.
2 The same as the previous function, but with a different syntax. The /1 after is_integer means that the function takes one parameter.
3 You can use multiple parameters too (e.g. &1, &2).

Sometimes it is more convenient to use the & operator, but there are times when it makes the expression more difficult to read.

Variable Scopes

In every programming language variables have some sort of scope. Let’s have a look into some code to figure out how variables in Elixir are scoped:

iex(1)> area = 5 (1)
iex(2)> IO.puts(area)
iex(3)> square_area = fn a -> (2)
...(3)>   area = a * a (3)
...(3)>   area
...(3)> end
#Function<7.126501267/1 in :erl_eval.expr/5>
iex(4)> square_area.(10) (4)
iex(5)> IO.puts(area) (5)
1 We bind the value of 5 to the variable area.
2 We define an anonymous function.
3 Within this function we bind the result of our calculation to another variable area.
4 Run the function with an argument of 10. That would mean that the area in the function gets set to the value 100.
5 The original area hasn’t changed a bit. Because it is in a different scope.

The area within the function is in an inner scope. The original area is in an outer scope.

But it gets a bit more complex:

iex(1)> pi = 3.14159265359 (1)
iex(2)> circular_area = fn radius -> pi * radius * radius end (2)
#Function<7.126501267/1 in :erl_eval.expr/5>
iex(3)> circular_area.(10)
1 We bind the value 3.14159265359 to the variable with the name pi.
2 We create an anonymous function which uses the variable pi to make the calculation.

So we can read the outer scope variable from within the function. So lets check if we can change it too:

iex(1)> pi = 3.14159265359 (1)
iex(2)> circular_area = fn radius ->
...(2)>   pi = 3.14 (2)
...(2)>   pi * radius * radius
...(2)> end
#Function<7.126501267/1 in :erl_eval.expr/5>
iex(3)> circular_area.(10) (3)
iex(4)> IO.puts(pi) (4)
1 We bind the value 3.14159265359 to the variable with the name pi.
2 We bind the inner scoped variable pi with the value 3.14.
3 The 3.14 and not the 3.14159265359 gets used.
4 The outer scoped pi is not changed.
You can not change the value of an outer scoped variable, but you can read it. And you can create a new inner scope variable with the same name without interacting with the outer scoped one.

Keyword Lists, Maps and Structs

List and Tuples don’t provide the functionality to access values with a key. We can achieve that functionality with keyword lists, maps and structs.

Keyword Lists

Keyword lists are key-value data structures, in which keys are atoms and keys can appear more than once.

iex(1)> user = [{:name, "joe"}, {:age, 23}] (1)
[name: "joe", age: 23]
iex(2)> user = [name: "joe", age: 23] (2)
[name: "joe", age: 23]
iex(3)> user[:name] (3)
iex(4)> new_user = [name: "fred"] ++ user
[name: "fred", name: "joe", age: 23]
iex(5)> new_user[:name] (4)
1 Keyword lists are lists of 2-item tuples, with the first item of each tuple being an atom.
2 This [key: value] syntax is more commonly used (this expression is the same as the list of tuples above).
3 The keyword list name followed by the key name in brackets returns a value for the given key.
4 If there are duplicate keys in a keyword list, the first one is fetched on lookup.

In your Phoenix application, you will see a keyword list used as the last argument in the render/3 function:

render(conn, "show.html", message: "Hello", username: "Mary") (1)
1 [message: "Hello", username: "Mary"] is a keyword list. As you can see from this example, the brackets are optional.

Keyword List Functions

The Keyword module offers functions for working with keyword lists.

Here are a few examples:

iex(1)> Keyword.get([age: 34, height: 155], :height)
iex(2)> Keyword.delete([length: 78, width: 104], :length)
[width: 104] (1)
1 After deleting the :length, the keyword list just contains the :width key-value pair.


Maps provide a way to store and retrieve key-value pairs. The %{} syntax creates a Map.

iex(1)> product_prices = %{"Apple" => 0.5, "Orange" => 0.7} (1)
%{"Apple" => 0.5, "Orange" => 0.7}
iex(2)> product_prices["Orange"] (2)
iex(3)> product_prices["Banana"] (3)
iex(4)> product_prices = %{"Apple" => 0.5, "Orange" => 0.7, "Apple" => 1}
warning: key "Apple" will be overridden in map

%{"Apple" => 1, "Orange" => 0.7} (4)
1 We create a new map and bind it to the variable product_prices.
2 The map name followed by the key name in brackets returns a value for the given key.
3 This returns nil if a given key doesn’t exist.
4 Unlike keyword lists, maps cannot contain duplicate keys.

But keys don’t have to be a specific type. Everything can be a key and a value:

iex(1)> %{"one" => 1, "two" => "abc", 3 => 7, true => "asdf"} (1)
%{3 => 7, true => "asdf", "one" => 1, "two" => "abc"}
iex(2)> %{"one" => 1, true => "asdf", true => "z"} (2)
warning: key true will be overridden in map

%{true => "z", "one" => 1}
1 A mixed bag of different types. Feel free to go wild.
2 A key has to be unique within a map. The last one overwrites the previous values. In this case, the key true will have a value of "z".

Atom keys

Using atoms as keys in maps gives you access to some nifty features:

iex(1)> product_prices = %{apple: 0.5, orange: 0.7} (1)
%{apple: 0.5, orange: 0.7}
iex(2)> product_prices.apple (2)
iex(3)> product_prices.banana (3)
** (KeyError) key :banana not found in: %{apple: 0.5, orange: 0.7}
1 With atoms as keys you can use this syntax which is a bit easier to read and less work to type.
2 When using atom keys, you can use the dot operator (.) to return the value of a given key.
3 If you use the dot operator and the key does not exist, an error is raised.

Map Functions

The Map module offers many useful functions for working with maps.

Here are just a few examples:

iex(1)> product_prices = %{apple: 0.5, orange: 0.7, coconut: 1}
%{apple: 0.5, coconut: 1, orange: 0.7}
iex(2)> Map.to_list(product_prices) (1)
[apple: 0.5, coconut: 1, orange: 0.7]
iex(3)> Map.values(product_prices) (2)
[0.5, 1, 0.7]
iex(4)> Map.split(product_prices, [:orange, :apple]) (3)
{%{apple: 0.5, orange: 0.7}, %{coconut: 1}}
iex(5)> a = Map.delete(product_prices, :orange) (4)
%{apple: 0.5, coconut: 1}
iex(6)> b = Map.drop(product_prices, [:apple, :orange]) (5)
%{coconut: 1}
iex(7)> additional_prices = %{banana: 0.4, pineapple: 1.2}
%{banana: 0.4, pineapple: 1.2}
iex(8)> Map.merge(product_prices, additional_prices) (6)
%{apple: 0.5, banana: 0.4, coconut: 1, orange: 0.7, pineapple: 1.2}
iex(9)> c = Map.put(product_prices, :potato, 0.2) (7)
%{apple: 0.5, coconut: 1, orange: 0.7, potato: 0.2}
1 Map.to_list/1 converts a map into a keyword list.
2 Map.values/1 returns the values of a map.
3 Map.split/2 splits a given map into two new maps. The first one contains all the key-value pairs which are requested by a list (e.g. [:orange, :apple])
4 Map.delete/2 deletes a specific key-value pair from a map.
5 Map.drop/2 deletes a list of key-value pairs from a map.
6 Map.merge/2 merges two maps.
7 Map.put/2 adds a key-value pair to a map.


A struct is a map that provides compile-time checks and default values. To define a struct you have to use the defstruct construct:

iex(1)> defmodule Product do (1)
...(1)>   defstruct name: nil, price: 0 (2)
...(1)> end
{:module, Product, ...
iex(2)> %Product{}
%Product{name: nil, price: 0}
iex(3)> apple = %Product{name: "Apple", price: 0.5} (3)
%Product{name: "Apple", price: 0.5}
iex(4)> apple
%Product{name: "Apple", price: 0.5}
iex(5)> apple.price
iex(6)> orange = %Product{name: "Orange"} (4)
%Product{name: "Orange", price: 0}
1 We define a new struct with the name Product and the keys name and price.
2 We define default values.
3 We define a new Product struct and set all values.
4 We define a new Product struct and set only the name. The price is set to the default value.

A struct guarantees that only the defined fields are allowed:

iex(7)> apple.description (1)
** (KeyError) key :description not found in: %Product{name: "Apple", price: 0.5}

iex(7)> banana = %Product{name: "Banana", weight: 0.1} (2)
** (KeyError) key :weight not found
    expanding struct: Product.__struct__/1
    iex:7: (file)
1 Since we didn’t define a description field in the Struct, we cannot access it.
2 Same with a new struct. There is no weight field defined. Therefore we can not set it.
Because structs are built on top of maps, they can be used with the same functions.

Pattern Matching

Pattern matching is essential in Elixir, and we have already used it, without knowing it, for binding values to variables.

iex(1)> a = 10 (1)
iex(2)> a
iex(3)> {b, c} = {10, 15} (2)
{10, 15}
iex(4)> b
iex(5)> c
iex(6)> {d, e} = 100
** (MatchError) no match of right hand side value: 100 (3)
1 This is actually a pattern match. The left side of = will be matched to the right site if possible.
2 Here we pattern match {b, c} on the left side with a tuple on the right side.
3 Boom! Because we can not match the {d, e} tuple with an integer we get a MatchError.

Since we don’t have much time, I’ll fast forward to match a head and tail of a list. Because there is a special syntax for that:

iex(1)> shopping_list = ["apple", "orange", "banana", "pineapple"] (1)
["apple", "orange", "banana", "pineapple"]
iex(2)> [head | tail] = shopping_list (2)
["apple", "orange", "banana", "pineapple"]
iex(3)> head
iex(4)> tail
["orange", "banana", "pineapple"]
iex(5)> [a | b] = tail (3)
["orange", "banana", "pineapple"]
iex(6)> a
iex(7)> b
["banana", "pineapple"]
iex(8)> [first_product, second_product | tail] = shopping_list (4)
["apple", "orange", "banana", "pineapple"]
iex(9)> first_product
iex(10)> second_product
iex(11)> tail
["banana", "pineapple"]
iex(12)> [first_product | [second_product | tail]] = shopping_list (5)
["apple", "orange", "banana", "pineapple"]
1 We match a list to the variable shopping_list.
2 [head | tail] is the special syntax to match a head and tail of a given list.
3 Again we match the head a and the tail b with tail.
4 A bit more complex. We match agains the first and second product followed by a tail.
5 Same result. Different syntax and logic. Pick the one you prefer.

Of course, if we know that a list has a specific number of elements we can match it directly:

iex(1)> shopping_list = ["apple", "orange", "banana", "pineapple"]
["apple", "orange", "banana", "pineapple"]
iex(2)> [a, b, c, d] = shopping_list
["apple", "orange", "banana", "pineapple"]
iex(3)> a
iex(4)> b
iex(5)> [e, f, g] = shopping_list (1)
** (MatchError) no match of right hand side value: ["apple", "orange", "banana", "pineapple"]
1 Just checking. You get an MatchError if Elixir can’t match both sides.

Matching Maps

Matching a Map works a little bit different to matching a Tuple or List. You can match just against the values you are interested in:

iex(1)> product_prices = %{apple: 0.5, orange: 0.7, pineapple: 1}
%{apple: 0.5, orange: 0.7, pineapple: 1}
iex(2)> %{orange: price} = product_prices (1)
%{apple: 0.5, orange: 0.7, pineapple: 1}
iex(3)> price
iex(4)> %{orange: price1, apple: price2} = product_prices (2)
%{apple: 0.5, orange: 0.7, pineapple: 1}
iex(5)> price1
iex(6)> price2
1 We can just match one value.
2 Or we can match multiple values. But we don’t have to match the whole Map.

Matching String parts

Easiest explained with a code example:

iex(1)> user = "Stefan Wintermeyer"
"Stefan Wintermeyer"
iex(2)> "Stefan " <> last_name = user
"Stefan Wintermeyer"
iex(3)> last_name
The left side of a <> operator in a match should always be a string. Otherwise, Elixir can’t verify it’s size.

Wildcard Matching

Sometimes you need pattern matching to get a value, but you don’t need all of the values in the pattern. For those cases, you can use _ (alone or as a prefix to a variable name). It indicates to Elixir that you don’t need that variable to be bound to anything.

iex(1)> shopping_list = ["apple", "orange", "banana", "pineapple"]
["apple", "orange", "banana", "pineapple"]
iex(2)> [first_product | _tail] = shopping_list (1)
["apple", "orange", "banana", "pineapple"]
iex(3)> first_product
iex(4)> [head | _] = shopping_list (2)
["apple", "orange", "banana", "pineapple"]
iex(5)> head
1 We pattern match the head of shopping_list to first_product. But we don’t need the tail, and we indicate that by prefixing it with a _.
2 We can use just a _ too. Using _tail just improves the code readability a bit.

Pattern Matching with Functions

Pattern matching is used everywhere in Elixir. You can even use it with Functions:

iex(1)> defmodule Area do
...(1)>   def circle(:exact, radius) do (1)
...(1)>     3.14159265359 * radius * radius
...(1)>   end
...(1)>   def circle(:normal, radius) do (2)
...(1)>     3.14 * radius * radius
...(1)>   end
...(1)>   def circle(radius) do (3)
...(1)>     circle(:normal, radius)
...(1)>   end
...(1)> end
{:module, Area, ...
iex(2)> Area.circle(:exact, 4)
iex(3)> Area.circle(:normal, 4)
iex(4)> Area.circle(4)
1 We define a circle/2 function which matches if the first argument is the atom :exact.
2 We define a circle/2 function which matches if the first argument is the atom :normal.
3 We define a circle/1 function which calls the cirle/2 function with the :normal argument.

Functions with Guards

Guards add some additional spices to pattern matching with functions. You can find all the details at https://hexdocs.pm/elixir/guards.html

Here are just some examples to show you the concept. Guards start with when:

iex(1)> defmodule Law do
...(1)>   def can_vote?(age) when is_integer(age) and age > 17 do (1)
...(1)>     true
...(1)>   end
...(1)>   def can_vote?(age) when is_integer(age) do (2)
...(1)>     false
...(1)>   end
...(1)>   def can_vote?(_age) do (3)
...(1)>     raise ArgumentError, "age should be an integer"
...(1)>   end
...(1)> end
{:module, Law, ...
iex(2)> Law.can_vote?(15)
iex(3)> Law.can_vote?(20)
iex(4)> Law.can_vote?("test") (4)
** (ArgumentError) age should be an integer
    iex:4: Law.can_vote?/1
1 This guard checks if the age argument is an integer and the value of it is bigger than 17.
2 This guard just checks if the age argument is an integer.
3 This clause catches any value that is not called with an integer.
4 Since "test" is a string and not an integer, the ArgumentError that we wrote is raised.


case is a control structure which matches a given value to a couple of matching cases until one matches.

Let’s assume we want to create a function that converts morse coded numbers to integers:

iex(1)> defmodule Morse do
...(1)>   def morse_to_number(input) do
...(1)>     case input do (1)
...(1)>       "-----" -> 0 (2)
...(1)>       ".----" -> 1
...(1)>       "..---" -> 2
...(1)>       "...--" -> 3
...(1)>       "....-" -> 4
...(1)>       "....." -> 5
...(1)>       "-...." -> 6
...(1)>       "--..." -> 7
...(1)>       "---.." -> 8
...(1)>       "----." -> 9
...(1)>       _ -> :error (3)
...(1)>     end
...(1)>   end
...(1)> end
{:module, Morse, ...
iex(2)> Morse.morse_to_number("-....") (4)
1 After case comes the value we want to check.
2 "-----" is the expression we want to match to return a 0.
3 _ is the catch-all in case nothing matched yet. In this case, return an :error atom.
4 It works. :-)

Of course, we could solve this problem just with functions too. It’s up to you what makes the most sense in a given situation.

if and unless

if is common to many programming languages. unless is equivalent to if not. The following examples will show how to use them:

iex(1)> if 1 == 1 do
...(1)>   "Bingo!"
...(1)> else
...(1)>   "Negative"
...(1)> end
iex(2)> unless true do
...(2)>   "Never"
...(2)> end

Sometimes you see a one-line short form:

iex(3)> if 1 == 1, do: "Bingo!"
Most Elixir developers prefer case over if or unless.


Probably you have already heard about immutability in Elixir. What’s that about?

A variable points to a specific part of the memory where the data is stored. In many programming languages that data can be changed to update a variable. In Elixir, you can’t change it. So that doesn’t mean that you can’t rebind a variable to a different value but that this new value gets a new piece of memory and doesn’t overwrite the old memory. Once a function returns a result and therefore, has finished its work, everything gets garbage collected (wiped blank).

Why is that important at all? With immutable variables, we can be sure that other processes can not change their values while running parallel tasks. That has a massive effect. In the end, it means that your Phoenix application can run on multiple CPUs on the same server in parallel. It even means that your Phoenix application can share multiple CPUs on several nodes of a server cluster in your data center; this makes Elixir extremely scalable and save.

But doesn’t that make your application slower? Funny thing: No. This way is faster. It is not efficient to change data in memory.

But don’t worry. It is not as complicated as it sounds. Everytime you use a variable it uses the value of that moment in time. It will not be effected/changed afterwords:

iex(1)> product = "Orange"
iex(2)> test1 = fn -> IO.puts(product) end (1)
#Function<21.126501267/0 in :erl_eval.expr/5>
iex(3)> product = "Apple"
iex(4)> test2 = fn -> IO.puts(product) end
#Function<21.126501267/0 in :erl_eval.expr/5>
iex(5)> product = "Pineapple"
iex(6)> test3 = fn -> IO.puts(product) end
#Function<21.126501267/0 in :erl_eval.expr/5>
iex(7)> product = "Banana"
iex(8)> test1.() (2)
iex(9)> test2.()
iex(10)> test3.()
iex(11)> IO.puts(product)
1 Those anonymous functions may run on totally different CPUs. The life in their own little universe.
2 The value of product has changed multiple times. But for test1.() it is the value from that point in time when we created the function.


Until now encapsulated Strings in double quotes and we haven’t talked about char lists at all (IMO not needed for a beginners introduction). But there is one more mechanism to represent texts. They are called Sigils and start with a ~ (tilde) character which is followed by one letter which indicates what kind of sigil it is. After that, you can use a couple of different delimiters:

~r/example text/
~r|example text|
~r"example text"
~r'example text'
~r(example text)
~r[example text]
~r{example text}
~r<example text>
Elixir provides different delimiters for sigils so that you can write literals without escaped delimiters.

Regular expressions

~r marks a regular expression:

iex(1)> regex = ~r/bcd/
iex(2)> "abcde" =~ regex
iex(3)> "efghi" =~ regex
iex(4)> regex = ~r/stef/i (1)
iex(5)> "Stefan" =~ regex
1 Modifiers are supported too. For a complete list have a look at https://hexdocs.pm/elixir/Regex.html


You can use the ~s sigil to generate a string:

iex(1)> example = ~s(WOW! "double" and 'single' quotes without escaping)
"WOW! \"double\" and 'single' quotes without escaping"
iex(2)> IO.puts(example)
WOW! "double" and 'single' quotes without escaping

Sigils support heredocs too. You can use triple, double, or single quotes as separators:

iex(1)> example_text = ~s"""
...(1)> This is an example text.
...(1)> Multiple lines are not a problem.
...(1)> """
"This is an example text.\nMultiple lines are not a problem.\n"
iex(2)> IO.puts(example_text)
This is an example text.
Multiple lines are not a problem.


Word lists

The ~w sigil is a useful way to generate lists of words:

iex(1)> shopping_cart = ~w(apple orange banana)
["apple", "orange", "banana"]
iex(2)> shopping_cart_atoms = ~w(apple orange banana)a (1)
[:apple, :orange, :banana]
1 The a modifier tells Elixir to generate a list of atoms and not strings.

Date and Time

Elixir provides a couple of good to go time-related Struct[structs] which all have their sigil.


Elixir provides a %Date{} struct that contains the following fields:

  • year

  • month

  • day

  • calendar

With the ~D sigil you can create new %Date{} struct:

iex(1)> birthday = ~D[1973-03-23]
iex(2)> birthday.day
iex(3)> birthday.month
iex(4)> birthday.year


Elixir provides a %Time{} struct that contains the following fields:

  • hour

  • minute

  • second

  • microsecond

  • calendar

With the ~T sigil you can create new %Time{} struct:

iex(1)> now = ~T[09:29:00.0]
iex(2)> now.hour


The %NaiveDateTime{} struct mixes %Date{} with %Time{}.

With the ~N sigil you can create new %NaiveDateTime{} struct:

iex(1)> timestamp = ~N[2020-05-08 09:48:00]
~N[2020-05-08 09:48:00]


The %DateTime{} struct adds a timezone to a %NaiveDateTime{}.

With the ~U sigil you can create new %NaiveDateTime{} struct:

iex(4)> timestamp = ~U[2029-05-08 09:59:03Z]
~U[2029-05-08 09:59:03Z]
Find more information about timezones and DateTime at https://hexdocs.pm/elixir/DateTime.html


Recursions are often used when you would use a loop in an object-oriented language.

Let’s write a recursive function which provides a countdown:

iex(1)> defmodule Example do
...(1)>   def countdown(1) do (1)
...(1)>     IO.puts "1" (2)
...(1)>   end
...(1)>   def countdown(n) when is_integer(n) and n > 1 do (3)
...(1)>     IO.puts Integer.to_string(n) (4)
...(1)>     countdown(n - 1) (5)
...(1)>   end
...(1)> end
{:module, Example, ...
iex(2)> Example.countdown(4) (6)
1 If countdown/1 is called with the argument 1 this is the best match.
2 We call IO.puts("1") to print 1 to STDOUT.
3 If countdown/1 is called with an integer bigger than 1 as an argument this function matches.
4 We have to use Integer.to_string(n) to print the integer to STDOUT.
5 We recursively decrese n by 1 and call countdown/1 with that new number.
6 It works!

Here’s a different example where we calculate the sum of a list of integers:

iex(1)> defmodule Example do
...(1)>   def sum([]) do (1)
...(1)>     0
...(1)>   end
...(1)>   def sum([head | tail]) do (2)
...(1)>     head + sum(tail) (3)
...(1)>   end
...(1)> end
{:module, Example, ...
iex(2)> Example.sum([10, 8, 12, 150]) (4)
iex(3)> [head | tail] = [150] (5)
iex(4)> tail
1 The sum of an empty list is 0.
2 We pattern match a list and split it into a head and a tail.
3 We add the current head to the sum of the tail.
4 It works!
5 This is just to show how Elixir handles the case of a list with one element.

You can use the same concept to transform every element of a list. Let’s assume we want to double the value of every element of a list:

iex(1)> defmodule Example do
...(1)>   def double([]) do (1)
...(1)>     []
...(1)>   end
...(1)>   def double([head | tail]) do
...(1)>     [head * 2 | double(tail)] (2)
...(1)>   end
...(1)> end
{:module, Example, ...
iex(2)> Ex
Example      Exception
iex(2)> Example.double([10, 5, 999])
[20, 10, 1998]
1 We again start with the most simple match. An empty list. That will result in an empty list.
2 The [head | tail] syntax works both ways. We can use it to build a list too.

How to tackle a recursion

Unless you are doing this every day, you will get to problems where you know that recursion is a good solution, but you just can’t think of a good recursion for it.

Let me share a pro tip for these situations: https://www.google.com and https://stackoverflow.com are my lifesavers in such cases. No embarrassment!

During this book, we will work with recursions. So you’ll get a better feeling for it.


By now, you understand the basics of Elixir. The next step is to create an application. In the Elixir ecosystem, this is done with the (already installed) command-line interface (CLI) mix. Let’s do that for a "Hello world!" application:

$ mix new hello_world
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/hello_world.ex
* creating test
* creating test/test_helper.exs
* creating test/hello_world_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd hello_world
    mix test

Run "mix help" for more commands.

The command ´mix new projectname` creates a new directory with the name projectname and fills it with a default structure:

$ cd hello_world
$ tree
├── README.md
├── lib
│   └── hello_world.ex
├── mix.exs
└── test
    ├── hello_world_test.exs
    └── test_helper.exs

2 directories, 5 files

The Phoenix directory structure will be more involved but has the same core.

mix tasks

A task is a mechanism to start code with mix. For our "Hello world!" programme we have to create the directory lib/mix/tasks and create the file lib/mix/tasks/start.ex with this code:


defmodule Mix.Tasks.Start do
  use Mix.Task

  def run(_) do (1)
    IO.puts "Hello world!"
1 The run(_) function is the default function which gets called automatically.

Now we can start the mix start task:

$ mix start
Compiling 1 file (.ex)
Generated hello_world app
Hello world!

The .ex file gets compiled, and the start task gets run. The compile is only done when needed. If we call mix start a second time no compile is needed:

$ mix start
Hello world!

Obviously mix as a topic is much more complicated. In this section, I just wanted to show you the very basic idea of mix so that you know where to search if you want to know what happens if you do a mix server with a Phoenix application.

mix format

You are going to love mix format. You can call it in the root directory of your Phoenix application and it will autoformat all your Elixir source code files.

You should use mix format every time you are going to commit code to a repository.

What else?

This chapter just deals with the tip of the iceberg. It provides the basic knowledge that you need to start with the Phoenix Framework. There is a lot more to learn. But I wouldn’t worry too much about that right now. You are good to go for the next chapter of this book. Have fun!

Elixir Books

If you want to dive more into Elixir than I recommend the following books: