100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Elixir Quick Reference

A condensed cheat-sheet of core Elixir syntax, data types, collection tradeoffs, and standard-library functions for quick lookup.

PracticeBeginner7 min readJul 10, 2026
Analogies

Core Syntax and Data Types at a Glance

Elixir's core data types are integers and floats (arbitrary-precision integers, no overflow), atoms (constants whose name is their own value, written :ok or :error), booleans (which are actually just the atoms true/false), binaries/strings (UTF-8 encoded, double-quoted), lists ([1, 2, 3], linked-list under the hood), tuples ({:ok, 42}, fixed-size and index-fast), and maps (%{name: "Ada", age: 30}, key-value with average O(1) access). Variables are bound with = (which is actually pattern matching, not assignment), and both modules and functions are defined with defmodule/def, where every function's arity (its argument count) is part of its identity, so greet/1 and greet/2 are considered entirely different functions that happen to share a name.

🏏

Cricket analogy: An atom like :ok is like a fixed cricketing term such as 'LBW' -- the word itself is the entire meaning, unlike a variable score that changes every ball; a function's arity mattering is like how 'over' means something completely different in a 6-ball over count versus 'run over,' context (argument count) changes identity.

Collections: Lists, Tuples, and Maps

Lists are linked lists -- prepending with [0 | list] is O(1), but indexing or appending to the end is O(n), so lists are best for sequential processing and pattern matching ([head | tail]), not random access. Tuples are fixed-size and stored contiguously, so elem(tuple, 0) is O(1), making them ideal for small, fixed-shape data like function return values ({:ok, result} / {:error, reason}) where the shape never changes at runtime. Maps offer average O(1) key lookup and are updated either with Map.put(map, :key, value) (works even if :key doesn't exist yet) or the more restrictive %{map | key: value} update syntax, which is faster but raises a KeyError if :key isn't already present -- a subtle but important distinction.

🏏

Cricket analogy: A list is like a batting order card where you can quickly slot a new opener at the top (O(1) prepend) but finding out who's batting at number 9 means reading down the whole card sequentially (O(n)); a tuple is like a fixed three-result scoreboard (win/loss/tie) where any slot is instantly readable.

The Pipe Operator and Enum/Stream Basics

The pipe operator |> takes the expression on its left and inserts it as the first argument of the function call on its right, so data |> Enum.filter(&(&1 > 0)) |> Enum.map(&(&1 * 2)) reads top-to-bottom as a data transformation pipeline instead of nesting Enum.map(Enum.filter(data, ...), ...) inside-out. Enum functions are eager -- each step in a pipeline fully processes the entire collection and builds an intermediate list before the next step runs -- while Stream functions are lazy, building up a description of the transformations that only actually runs when a terminal function like Enum.to_list/1 or Enum.take/2 pulls values through, which matters a lot for large or infinite sequences (Stream.iterate/2, Stream.cycle/1) where materializing the whole thing eagerly would be wasteful or impossible.

🏏

Cricket analogy: The pipe operator is like a fielding relay drill -- catch, throw, catch, throw -- read left to right, rather than a nested description of who caught whose throw; Stream's laziness is like a bowler only running in to bowl the specific delivery the captain calls, not pre-bowling every possible delivery in advance.

elixir
# Core types
i = 42
f = 3.14
atom = :ok
str = "hello"
list = [1, 2, 3]
tuple = {:ok, 42}
map = %{name: "Ada", age: 30}

# Map update syntax
map2 = Map.put(map, :city, "London")     # adds or updates :city
map3 = %{map | age: 31}                  # requires :age to already exist

# Module and function definition
defmodule Greeter do
  def greet(name), do: "Hello, #{name}!"
  def greet(name, greeting), do: "#{greeting}, #{name}!"
end

# Pipe + Enum
[1, -2, 3, -4, 5]
|> Enum.filter(&(&1 > 0))
|> Enum.map(&(&1 * 2))
|> Enum.sum()
# => 18

# case / cond
case Greeter.greet("Ada") do
  "Hello, " <> _ -> :greeted
  _ -> :unknown
end

Start iex -S mix inside a project to get a REPL with your app's modules loaded; typing h Enum.map inside iex prints that function's documentation directly, and i value inspects any value's type and protocol implementations -- both are faster than searching docs in a browser while learning.

== performs value comparison and treats 1 == 1.0 as true (numeric equality across int/float), while === (strict comparison) treats 1 === 1.0 as false because the types differ. This distinction has bitten many newcomers when comparing IDs or amounts fetched from different sources -- one as an integer, one as a float -- and getting an unexpected match with ==.

Common Standard Library Cheat Points

The String module operates on UTF-8 binaries and is grapheme-aware (String.length/1 counts user-perceived characters correctly even for multi-byte scripts, unlike byte_size/1), with common functions like String.split/2, String.trim/1, String.replace/3, and String.upcase/1 covering the vast majority of text-processing needs. A frequent beginner confusion is when to reach for Enum versus List: Enum works polymorphically over anything implementing the Enumerable protocol (lists, maps, ranges, streams), while List contains functions specific to list structure like List.flatten/1 or List.first/1 -- in practice, Enum is the default choice unless you need a list-specific operation. On the tooling side, mix new my_app scaffolds a project, mix deps.get fetches dependencies declared in mix.exs, mix test runs ExUnit, and mix format auto-formats code to the community-standard style.

🏏

Cricket analogy: String.length/1 counting graphemes correctly is like a scorer correctly counting a player's actual name (say, a name with diacritics) as intended rather than miscounting based on raw byte encoding; Enum's polymorphism is like a single scoring system that works whether you're tracking a Test match, ODI, or T20 without needing a separate scorer for each format.

  • Elixir's core types include integers/floats, atoms, booleans (true/false atoms), UTF-8 binaries/strings, lists, tuples, and maps.
  • Function identity includes arity -- greet/1 and greet/2 are entirely different functions even with the same name.
  • Lists are linked lists (fast prepend, O(n) index/append); tuples are fixed-size with O(1) indexed access; maps offer average O(1) key lookup.
  • Map.put/3 adds or updates a key even if absent; the %{map | key: value} syntax is faster but raises KeyError if the key doesn't already exist.
  • The pipe operator |> threads a value as the first argument into the next function call, reading top-to-bottom instead of nesting calls inside-out.
  • Enum functions are eager and materialize intermediate collections; Stream functions are lazy and only evaluate when pulled by a terminal function.
  • == allows cross-type numeric equality (1 == 1.0 is true); === is strict and requires matching types (1 === 1.0 is false).

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ElixirStudyNotes#ElixirQuickReference#Elixir#Quick#Reference#Core#StudyNotes#SkillVeris#ExamPrep