Elixir was created by José Valim and first released in 2011. It is a functional, concurrent language built on the Erlang VM (BEAM), which was originally developed for telecom applications by Ericsson in the 1980s.
Elixir was designed to bring modern features and improved developer experience to the Erlang ecosystem, including metaprogramming, a more user-friendly syntax, and tools to better support large-scale, concurrent systems.
Valim created Elixir because he wanted a language that could offer the same reliability and performance as Erlang but with a more modern syntax and tooling. Elixir leverages Erlang’s lightweight process model and fault-tolerant design, which is crucial for building distributed and scalable systems.
Since its release, Elixir has become popular in fields like real-time web applications, distributed systems, and microservices, largely due to its focus on concurrency, scalability, and fault tolerance.
Who:
Elixir was created by José Valim, a Brazilian software engineer who had previously worked with the Ruby on Rails community. He saw the opportunity to build a language that would harness the power of Erlang’s concurrency model while making it more approachable for modern developers.
Elixir is open-source and is maintained by the Elixir Core Team and the Elixir community. The language has seen growing support and adoption from companies and developers building scalable, high-performance systems.
Why:
Elixir was created to provide a modern, functional programming language that could leverage the power of Erlang’s actor-based concurrency model, enabling developers to build concurrent, distributed, and fault-tolerant systems.
The language aims to improve the developer experience compared to Erlang by offering a more accessible syntax, robust tooling (including the Mix build tool and ExUnit testing framework), and support for metaprogramming.
Elixir’s focus on concurrency, fault tolerance, and distributed computing makes it ideal for real-time applications, such as messaging systems, web services, and other systems requiring high availability and reliability.
Introduction
Advantages
High Concurrency & Scalability — Runs on Erlang BEAM VM, allowing millions of lightweight processes to execute concurrently. Perfect for high-traffic real-time systems.
Fault Tolerance — Follows Erlang’s “Let it crash” philosophy. Processes are isolated; failures are isolated and resolved using Supervision Trees.
Hot Code Upgrades — Allows code deployment and updates to a running server without down-time.
Immutability & Pure Functions — Side-effect-free programming makes code extremely easy to trace, debug, and test.
Excellent Developer Tooling — Bundled with Mix (project manager), ExUnit (test runner), Hex (package manager), and IEx (interactive shell).
Meta-programming — Macro system allows developers to extend the language syntax and write compile-time code generation.
Disadvantages
Steep Functional Paradigm Shift — Learning curve for developers transitioning from object-oriented styles (classes, mutability).
Ecosystem Size — Smaller overall community and library catalog compared to Python, JavaScript, or Java.
CPU-bound Tasks Performance — BEAM VM is optimized for highly concurrent, IO-bound operations (networking, databases). It can be slow for intense compute-heavy algorithms (scientific math, image processing).
No Static Types — Elixir is dynamically typed (though Dialyzer provides static type specs analysis).
Remember Points
Processes are NOT Threads — BEAM processes are extremely lightweight (~2KB memory overhead) and run inside isolated heaps.
State is Held in Processes — There is no global state. State is persisted by looping processes (like GenServer).
Pattern Matching is Key — Almost every statement (including assignments) is a pattern match operation.
Basics
Hello World & Entry Point
defmodule MyProject.Main do def hello do IO.puts "Hello, World!" endend# Run functionMyProject.Main.hello()
defmodule declares a namespace module in Elixir.
def defines a public function.
IO.puts prints a string to the console with a trailing newline.
Comments
# This is a single-line comment in Elixir.# There is no native multi-line comment block;# each line in a multi-line comment block must start with '#'@doc "Documentation for functions, used by ExDoc"def documented_function doend
Variables and Atoms
# Variables are bound to values. They can be rebound, but the data is immutable.name = "VR Rathod"age = 25# Atoms (Constants whose name is their value, similar to Symbols in Ruby):ok:error:not_found# Booleans are just atoms underneath:# true is equivalent to :true# false is equivalent to :false
Data Types Table
Type Example Description
Integer 42, 0x1F Standard integer
Float 3.14 Floating-point number
Boolean true, false Booleans (atoms underneath)
Atom :ok, :error Constant names
String "Hello" UTF-8 encoded binary
List [1, 2, "three"] Linked list of values (heterogeneous)
Tuple {1, 2, :ok} Contiguous array of values
Map %{"key" => "value"} Key-value store
Operators & Pattern Matching
The Match Operator (=)
# In Elixir, '=' is the match operator, not assignmentx = 1 // Matches x with 1. Since x is unbound, it binds to 1.# Match check1 = x // Matches 1 with value of x (1). Success!# 2 = x // Raises MatchError
Destructuring
# Destructure Tuple{status, result} = {:ok, "success"} // status = :ok, result = "success"# Destructure List[head | tail] = [1, 2, 3] // head = 1, tail = [2, 3]
The Pin Operator (^)
# Use pin operator to match against a variable's existing value rather than rebinding it.x = 10# {^x, y} = {20, 30} // Raises MatchError (expects first element to be 10){^x, y} = {10, 30} // y = 30
Control Flow
Conditionals: if, unless, cond
# 1. Ifif age >= 18 do "Adult"else "Minor"end# 2. Unless (negated if)unless is_authorized do "Access Denied"end# 3. Cond (evaluates first expression that evaluates to truthy)cond do score >= 90 -> "Grade A" score >= 80 -> "Grade B" true -> "Grade F" # default fallbackend
Case Expressions
# Match on structural shape and valuescase File.read("data.txt") do {:ok, contents} -> IO.puts("Contents: #{contents}") {:error, :enoent} -> IO.puts("File not found.") {:error, reason} -> IO.puts("Failed: #{reason}")end
With Expressions
# with sequences matching steps. If any step fails to match,# it breaks early and returns the unmatched term.with {:ok, user} <- get_user(1), {:ok, profile} <- get_profile(user.id), {:ok, address} <- parse_address(profile.address) do {:ok, {user, address}}else {:error, reason} -> {:error, "Chaining failed due to: #{reason}"}end
Recursion (No Loops)
# Loop through recursiondefmodule Math do def sum_list([], acc), do: acc def sum_list([head | tail], acc), do: sum_list(tail, acc + head)endMath.sum_list([1, 2, 3], 0) # 6
defmodule Account do # Public function def check_eligibility(age) when age >= 18 do :ok end # Overloaded body for pattern match def check_eligibility(_age) do {:error, :underage} end # Private function (defp) defp secret_key do "123" endend
The Pipe Operator (|>)
# Passes result of expression as first argument of next functionstring = " hello world "# Traditionalres = String.capitalize(String.trim(string))# Piped (Highly readable flow)res = string |> String.trim() |> String.capitalize() # "Hello world"
Processes & Concurrency
Spawning Processes
# Spawn creates a new BEAM lightweight process executing given functionpid = spawn(fn -> IO.puts("Running in background process")end)
# Simplifies running background process to store state{:ok, agent} = Agent.start_link(fn -> [] end)Agent.update(agent, fn state -> ["Item 1" | state] end)items = Agent.get(agent, fn state -> state end) # ["Item 1"]
GenServer (Generic Server Process)
defmodule QueueServer do use GenServer # Client API def start_link(initial_state) do GenServer.start_link(__MODULE__, initial_state) end def enqueue(pid, element) do GenServer.cast(pid, {:enqueue, element}) # asynchronous end def dequeue(pid) do GenServer.call(pid, :dequeue) # synchronous end # Server Callbacks @impl true def init(state) do {:ok, state} end @impl true def handle_cast({:enqueue, element}, state) do {:noreply, state ++ [element]} end @impl true def handle_call(:dequeue, _from, [head | tail]) do {:reply, head, tail} endend
Supervision Trees
# Supervisors monitor worker processes and restart them upon failurechildren = [ {QueueServer, [1, 2, 3]}]# Start supervisor with One-For-One strategy:# if a child process dies, only that process is restarted.Supervisor.start_link(children, strategy: :one_for_one)
Exception Handling
Try, Rescue, Raise
try do raise "Some error occurred"rescue e in RuntimeError -> IO.puts("Rescued: #{e.message}")after IO.puts("Finished")end
File I/O
Reading & Writing
# Write to fileFile.write("config.txt", "port=4000\nhost=localhost\n")# Read from filecase File.read("config.txt") do {:ok, binary} -> IO.puts(binary) {:error, reason} -> IO.puts("Error: #{reason}")end
Metaprogramming
Macros: Quote and Unquote
# Macros inject code blocks into compile-time ASTdefmodule Assertion do defmacro assert(expression) do quote do if !unquote(expression) do raise "Assertion failed!" end end endend
Build Tools & Environment
Mix Commands
Mix is the build tool shipped with Elixir.
# Create new project templatemix new my_app# Compile projectmix compile# Run interactive IEx console with project code loadediex -S mix# Run testsmix test
Useful Libraries & Frameworks
Phoenix - Scalable, highly performant real-time web framework (like Rails but faster).
Ecto - Data database mapping, validation and query DSL.
Nerves - Build and deploy real-time firmware for embedded systems.
Jason - Fast, pure-Elixir parser for JSON strings.