Elixir essentials: 50 cards on pattern matching, processes and OTP
Elixir fundamentals with short snippets: immutability, pattern matching, the pipe operator, Enum vs Stream, processes, GenServer, supervisors and mix.
Practice now
Study without an account
Scheduled with the default Askesis timings. Progress is saved in this browser.
All 50 cards
- 1Can you change a list or map in place in Elixir?No. Data is immutable: functions return new values, and
x = x + 1rebinds the namexto a new value without changing the old one - 2What does
=do in Elixir?It’s the match operator: it matches the right side against the pattern on the left, binding any variables, and raisesMatchErrorif they don’t matchelixir{:ok, body} = {:ok, "hello"} # body is "hello" {:ok, body} = {:error, :enoent} # ** (MatchError) - 3What does the pin operator
^do?Matches against a variable’s current value instead of rebinding the variableelixirx = 1 x = 2 # rebinds x ^x = 3 # ** (MatchError), because x is 2 - 4What are atoms?Constants whose name is their value, such as
:okand:error;true,falseandnilare atoms tooAtoms are never garbage collected, so don’t create them from user input withString.to_atom/1. - 5Lists vs tuplesLists are linked lists: prepending with
[x | list]is fast, but length and index access are linear. Tuples are stored contiguously: index access is fast, but any update copies the whole tuple - 6Keyword list vs mapA keyword list (
[name: "Ada", role: :admin]) is a list of{atom, value}tuples that keeps order and allows duplicate keys, typical for options; a map (%{name: "Ada"}) has unique keys of any type and fast lookup - 7
map.keyvsmap[:key]For an atom key,map.keyraisesKeyErrorwhen the key is missing, whilemap[:key]returnsnil - 8Update an existing key in a mapelixir
user = %{user | age: 37}This syntax raisesKeyErrorif:ageisn’t already in the map;Map.put/3adds or replaces. - 9Define a struct with a default valueelixir
defmodule User do defstruct [:name, age: 0] end %User{name: "Ada"} # age defaults to 0Unknown keys are a compile-time error, and structs don’t supportuser[:name]access. - 10
"abc"vs~c"abc""abc"is a string, a UTF-8 encoded binary;~c"abc"is a charlist, a list of code points used mainly with Erlang librariesOlder code writes charlists with single quotes:'abc'. - 11What do
String.length("héllo")andbyte_size("héllo")return?5 and 6:String.length/1counts graphemes, whilebyte_size/1counts bytes, and é takes two bytes in UTF-8 - 12Which values are falsy in Elixir?Only
falseandnil; everything else, including0,""and[], is truthy - 13Define a function with several clauses and a guardelixir
defmodule Math do def fact(0), do: 1 def fact(n) when n > 0, do: n * fact(n - 1) endClauses are tried from top to bottom; the first one whose pattern and guard match runs. - 14What can you call in a guard?Only a fixed set of expressions: comparisons, boolean operators, arithmetic, type checks like
is_integer/1and a fewKernelfunctions such aslength/1Your own functions aren’t allowed; build reusable guards withdefguard. - 15What does
[head | tail] = [1, 2, 3]bind?headto1andtailto[2, 3]Matching[head | tail]against[]raises aMatchError. - 16Branch on an
{:ok, _}or{:error, _}resultelixircase File.read("config.json") do {:ok, body} -> parse(body) {:error, reason} -> {:error, reason} end - 17What is
withfor?Chaining pattern matches: each<-clause must match to continue, and the first value that doesn’t match is returned (or handled inelse)elixirwith {:ok, body} <- File.read(path), {:ok, data} <- Jason.decode(body) do {:ok, data} end - 18When do you use
cond?For a chain of conditions that aren’t patterns: the first truthy one wins, so end withtrue ->as a catch-allIf no condition is truthy,condraises aCondClauseError. - 19Give a function parameter a default valueelixir
def greet(name, greeting \\ "Hello"), do: "#{greeting}, #{name}" - 20
File.read/1vsFile.read!/1File.read/1returns{:ok, contents}or{:error, reason}; the!version returns the contents directly and raises on failureMany standard library functions come in this pair. - 21What does
|>do?Passes the result of the expression on its left as the first argument of the function call on its rightelixir"hello world" |> String.split() |> Enum.map(&String.capitalize/1) |> Enum.join(" ") # "Hello World" - 22What do
&String.upcase/1and&(&1 * 2)mean?Both use the capture operator: the first refers to a named function by name and arity, the second is shorthand forfn x -> x * 2 end - 23Why is an anonymous function called with a dot, as in
add.(1, 2)?The dot marks a call to a function stored in a variable, so it can’t be confused with a call to a named functionadd(1, 2) - 24What does
/2mean inEnum.map/2?The arity: the number of arguments. Functions with the same name and different arities are separate functions - 25Why can
IO.inspect/2sit in the middle of a pipeline?It prints its argument and returns it unchangedelixirorders |> Enum.filter(&paid?/1) |> IO.inspect(label: "paid") |> Enum.map(& &1.total) - 26
EnumvsStreamEnumfunctions are eager and build a full result at each step;Streamfunctions are lazy and compose into a single pass that runs only when something like anEnumfunction consumes it - 27What does this return?elixir
1..1_000_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(&(rem(&1, 2) == 0)) |> Enum.take(5)[6, 12, 18, 24, 30], after processing only the first ten numbers of the range - 28Sum a list with
Enum.reduce/3elixirEnum.reduce([1, 2, 3], 0, fn x, acc -> x + acc end) # 6The function receives the element first and the accumulator second. - 29What does
for x <- 1..5, rem(x, 2) == 1, do: x * xreturn?[1, 9, 25]: a comprehension with a generator and a filter - 30Count the lines containing
ERRORin a large file without loading it allelixirFile.stream!("app.log") |> Stream.filter(&String.contains?(&1, "ERROR")) |> Enum.count()File.stream!/1reads the file lazily, line by line. - 31What is an Elixir process?A lightweight process run by the BEAM virtual machine, with its own memory and mailbox; it isn’t an OS thread, and one system can run millions of them
- 32Spawn a process and send it a messageelixir
pid = spawn(fn -> receive do {:ping, from} -> send(from, :pong) end end) send(pid, {:ping, self()}) - 33Does
send/2wait for the receiver?No. It puts the message in the receiver’s mailbox and returns at once;receiveis what blocks, until a matching message arrives - 34Stop waiting in
receiveafter five secondselixirreceive do {:result, value} -> value after 5_000 -> :timeout end - 35What happens to messages that match no
receiveclause?They stay in the mailbox, andreceivekeeps waiting for one that matches; unmatched messages pile up - 36Link vs monitorA link is two-way: when one linked process crashes, the other gets an exit signal and crashes too unless it traps exits. A monitor is one-way: the watcher just receives a
{:DOWN, ref, :process, pid, reason}messagespawn_link/1andProcess.monitor/1set them up. - 37Run work concurrently and wait for its resultelixir
task = Task.async(fn -> fetch_report() end) report = Task.await(task)Task.await/2exits the caller if the task takes longer than 5 seconds, unless you pass a timeout. - 38What is a GenServer?An OTP behaviour for a process that keeps state and handles requests in a loop; you implement callbacks such as
init/1,handle_call/3andhandle_cast/2 - 39
GenServer.callvsGenServer.castcallis synchronous and waits for a reply, for up to 5 seconds by default;castis asynchronous and returns:okimmediately - 40A minimal counter GenServerelixir
defmodule Counter do use GenServer def start_link(n), do: GenServer.start_link(__MODULE__, n, name: __MODULE__) def increment, do: GenServer.cast(__MODULE__, :inc) def value, do: GenServer.call(__MODULE__, :value) @impl true def init(n), do: {:ok, n} @impl true def handle_cast(:inc, n), do: {:noreply, n + 1} @impl true def handle_call(:value, _from, n), do: {:reply, n, n} end - 41What is
handle_info/2for?Messages that arrive by plainsendrather than throughcallorcast, such as timer messages and monitor:DOWNmessages - 42What does a supervisor do?Starts child processes and restarts them, following a strategy, when they crash
- 43Supervisor strategies
:one_for_one,:one_for_alland:rest_for_oneRestart only the crashed child; restart every child; restart the crashed child and the children started after it - 44Start children under a supervisorelixir
children = [ {Counter, 0}, {Task.Supervisor, name: MyApp.TaskSupervisor} ] Supervisor.start_link(children, strategy: :one_for_one){Counter, 0}works becauseuse GenServerdefineschild_spec/1, which callsCounter.start_link(0). - 45Child restart values
:permanent,:transientand:temporary:permanentchildren are always restarted (the default);:transientones only after an abnormal exit;:temporaryones never - 46What does “let it crash” mean?Don’t write defensive code for every unexpected error: let the process crash and have its supervisor restart it in a known good state
- 47Create a new project with a supervision treebash
mix new my_app --sup--supgenerates an application module that starts a supervisor. - 48Fetch dependencies, run the tests and open a shell with the project loadedbash
mix deps.get mix test iex -S mix - 49Where do you declare a project’s dependencies?In the
deps/0function ofmix.exselixirdefp deps do [{:jason, "~> 1.4"}] end - 50What does the requirement
"~> 1.4"allow?Any version from 1.4.0 up to, but not including, 2.0.0"~> 1.4.2"is stricter: from 1.4.2 up to, but not including, 1.5.0.
Frequently asked questions
Do I need to know Erlang?
No. Every card uses Elixir syntax. Erlang and the BEAM virtual machine only come up where they explain how processes behave.
Which Elixir version do the cards target?
Current stable Elixir. Details that changed between versions, such as how charlists are written, are noted on the card.
Does it cover Phoenix?
No. It covers the language, processes and OTP that Phoenix is built on, which makes Phoenix code much easier to follow.
More in Programming
JavaScript
Master the JavaScript every developer uses daily: scope, closures, equality, array methods, promises, async/await and the event loop.
50 cardsAdvanced JS
Fifty advanced JavaScript cards: event-loop ordering puzzles, generators and async iteration, this binding, Proxy and Reflect, modules, AbortController and coercion edge cases.
50 cardsTypeScript
Learn the TypeScript you use every day: narrowing, generics, keyof, mapped and conditional types, utility types, satisfies and strict mode, with code on every card.
50 cardsPython
Learn idiomatic Python fast: comprehensions, f-strings, args and kwargs, generators, decorators, context managers and the standard library.
50 cardsGit & terminal
The Git commands and shell skills developers use every day: branching, rebasing, undoing mistakes, pipes, redirects, permissions and processes.
50 cardsJava
Core Java with short snippets: equals vs ==, records, sealed types, collections, generics, exceptions, streams, lambdas and JVM basics.
50 cardsC#
C# and .NET fundamentals with short snippets: value vs reference types, properties, records, nullable reference types, LINQ, async/await and DI.
50 cardsKotlin
Kotlin fundamentals with short snippets: null safety, val vs var, data and sealed classes, extension functions, scope functions and coroutines.
50 cardsSwift
Swift fundamentals with short snippets: optionals, structs vs classes, protocols, enums with associated values, closures, ARC, async/await and SwiftUI.
50 cardsFlutter
Flutter and Dart basics: null safety, futures and streams, stateless vs stateful widgets, setState, hot reload, keys, layout and state management.
50 cardsPHP
Modern PHP 8 with short snippets: strict types, arrays, match, nullsafe and named arguments, enums, readonly, Composer, PDO and password_hash.
50 cardsRuby
Ruby fundamentals with short snippets: blocks, procs and lambdas, symbols, Enumerable, mixins, truthiness, method_missing, gems and Bundler.
50 cardsGo
Go (Golang) fundamentals with short, idiomatic snippets: slices vs arrays, maps, defer, error wrapping, interfaces, goroutines, channels, select and context.
50 cardsRust
Learn the Rust that the compiler keeps asking about: ownership, borrowing, lifetimes, Option and Result, traits, closures, smart pointers and async.
50 cardsC++
Modern C++ in short snippets: references vs pointers, RAII, smart pointers, move semantics, STL containers and algorithms, const, templates and UB.
50 cardsHaskell
Haskell fundamentals with short code: types and type classes, pattern matching, laziness, Maybe and Either, folds, Functor, Applicative and Monad.
50 cardsx86-64 assembly
x86-64 assembly in Intel syntax: registers and sub-registers, flags, mov and lea, jumps, the stack frame, the System V calling convention and Linux syscalls.
50 cardsRegex
Learn regular expressions for good: anchors, classes, greedy vs lazy quantifiers, groups, lookarounds, flags, replacements and the backtracking trap.
50 cards