Skip to content
Elixir: Getting Started with Functional Programming

Elixir: Getting Started with Functional Programming

DodaTech Updated Jun 20, 2026 6 min read

Elixir is a dynamic, functional programming language built on the Erlang Virtual Machine (BEAM). It combines Ruby-inspired syntax with battle-tested concurrency primitives, making it ideal for building scalable, fault-tolerant systems.

In this tutorial, you’ll learn Elixir’s core concepts: functional programming with immutability, pattern matching, the pipe operator, the Mix build tool, the BEAM VM architecture, and a quick look at the Phoenix web framework.

What You’ll Learn

  • Elixir’s relationship with the BEAM VM and Erlang ecosystem
  • Functional programming: immutability, pure functions, recursion
  • Pattern matching — Elixir’s superpower
  • The pipe operator (|>) for data transformation pipelines
  • Mix build tool for project management
  • OTP (Open Telecom Platform) and concurrency
  • Phoenix web framework introduction

Why Elixir Matters

Elixir powers high-traffic systems like Discord’s voice infrastructure, Pinterest’s real-time notifications, and Bleacher Report’s mobile APIs. At DodaTech, we evaluate Elixir for real-time collaboration features in DodaZIP where concurrent file operations must be fault-tolerant.

Learning Path

    flowchart LR
  A[Functional Programming] --> B[Elixir Basics<br/>You are here]
  B --> C[Pattern Matching]
  C --> D[Mix & OTP]
  D --> E[Phoenix Framework]
  style B fill:#f90,color:#fff
  

The BEAM VM

The BEAM (Bogdan’s Erlang Abstract Machine) is Elixir’s runtime. It provides:

FeatureWhat It Means
ProcessesLightweight (microseconds to spawn, 1KB memory each)
Message passingShare nothing — communicate via messages
Supervision treesRestart failed components automatically
Hot code swappingUpdate code without stopping the system

Millions of processes can run concurrently on the BEAM. Compare this to OS threads (MB each) or goroutines (KB each). Elixir processes are measured in bytes.

Pattern Matching

In most languages, = is assignment. In Elixir, it’s a match operator:

# Basic matching
x = 42        # Binds 42 to x
42 = x        # 42 = 42 -> true
# 43 = x      # MatchError! 43 != 42

# Tuple matching
{status, value} = {:ok, "hello"}
IO.puts(status)  # ok
IO.puts(value)   # hello

# List matching
[head | tail] = [1, 2, 3, 4]
IO.puts(head)   # 1
IO.inspect(tail) # [2, 3, 4]

# Pin operator — match against existing value
name = "Alice"
# ^name = "Bob"  # MatchError — ^name pins to "Alice"

Expected output: Pattern matching destructures data and assigns variables. The pin operator ^ forces matching against an existing value instead of rebinding.

Immutability

Variables in Elixir are immutable. Instead of modifying data, you create new data:

# Immutability example
list = [1, 2, 3]
new_list = [0 | list]  # Prepend, don't modify

IO.inspect(list)     # [1, 2, 3]  (unchanged)
IO.inspect(new_list) # [0, 1, 2, 3]

# Update a map
user = %{name: "Alice", age: 30}
updated = %{user | age: 31}

IO.inspect(user)    # %{name: "Alice", age: 30}  (unchanged)
IO.inspect(updated) # %{name: "Alice", age: 31}

Expected output: The original data is never modified. New data structures share memory with the originals via structural sharing (efficient).

The Pipe Operator

The pipe operator (|>) passes the result of one function as the first argument to the next. It creates readable data transformation pipelines:

# Without pipes — nested, hard to read
result = Enum.sort(Enum.filter(Enum.map(list, fn x -> x * 2 end), fn x -> x > 5 end))

# With pipes — reads top-to-bottom
result = list
  |> Enum.map(fn x -> x * 2 end)
  |> Enum.filter(fn x -> x > 5 end)
  |> Enum.sort()

IO.inspect(result) # [6, 8, 10] (assuming list = [1,2,3,4,5])

Expected output: [6, 8, 10]. The pipe chains operations: map multiplies by 2, filter keeps values > 5, sort orders ascending.

Mix Build Tool

Mix is Elixir’s build tool — it handles project creation, dependencies, testing, and more:

# Create a new project
mix new my_project

# Output:
# * creating README.md
# * creating lib/my_project.ex
# * creating test/my_project_test.exs
# * creating mix.exs

# Run tests
mix test

# Add dependency (in mix.exs)
# defp deps do
#   [{:phoenix, "~> 1.7"}]
# end

# Install dependencies
mix deps.get

# Run interactive shell
iex -S mix
# lib/my_project.ex
defmodule MyProject do
  @moduledoc """
  Documentation for MyProject.
  """

  @doc """
  Greets a user.
  """
  def greet(name) do
    "Hello, #{name}!"
  end
end
# Usage
iex -S mix
iex(1)> MyProject.greet("Alice")
# "Hello, Alice!"

Expected output: The mix new command scaffolds a complete project structure. iex -S mix loads the project into an interactive shell.

OTP and Concurrency

OTP (Open Telecom Platform) provides abstractions for concurrent programming:

# Spawn a process
defmodule Greeter do
  def greet do
    receive do
      {:hello, name} ->
        IO.puts("Hello, #{name}!")
        greet()  # Loop to handle more messages
    end
  end
end

# In iex
pid = spawn(&Greeter.greet/0)
send(pid, {:hello, "Alice"})
# Hello, Alice!

send(pid, {:hello, "Bob"})
# Hello, Bob!

Expected output: Each send delivers a message to the process. The process receives and handles messages sequentially in its mailbox. Durga Antivirus Pro uses similar actor-model patterns for concurrent file scanning.

Phoenix Web Framework

Phoenix is Elixir’s flagship web framework — think Ruby on Rails performance on steroids:

# Router (lib/my_app_web/router.ex)
defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/api", MyAppWeb do
    pipe_through :api

    get "/users", UserController, :index
    post "/users", UserController, :create
  end
end

Phoenix Channels provide real-time bidirectional communication via WebSocket — powering chat, notifications, and live updates.

Common Mistakes

1. Forgetting the Pin Operator in Pattern Matching

Using = inside a case or function head rebinds the variable. Use ^ to match against an existing value.

2. Modifying Data Instead of Transforming

You can’t mutate a list or map. Always create new data structures. The old ones remain untouched.

3. Using Loops Instead of Recursion

Elixir has no for/while loops. Use recursion and Enum functions (map, reduce, filter) instead.

4. Not Using the Pipe Operator

Nested function calls are hard to read. Use |> to create readable left-to-right pipelines.

5. Ignoring OTP Supervision

Spawning bare processes without supervision means crashes go unhandled. Always use OTP supervisors.

6. Blocking in a Process

A long-running synchronous operation blocks the process’s mailbox. Use Task.async for heavy computations.

Practice Questions

1. What is pattern matching in Elixir?

= is a match operator, not assignment. If the left side matches the right side, variables are bound. If not, a MatchError is raised.

2. What does the pipe operator do?

It passes the result of the left expression as the first argument to the function on the right, creating readable data transformation pipelines.

3. What is a BEAM process?

A lightweight unit of concurrency (not an OS thread). Processes share nothing and communicate via message passing.

4. What is Mix used for?

Project creation, dependency management, testing, compilation, and running tasks.

5. Challenge: Build a simple GenServer.

Create a GenServer that maintains a counter. Implement increment, decrement, and get_value calls.

Mini Project: URL Fetcher

Build a program that fetches multiple URLs concurrently:

defmodule URLFetcher do
  def fetch(url) do
    Task.async(fn ->
      {:ok, body} = HTTPoison.get(url)
      String.length(body)
    end)
  end

  def run(urls) do
    tasks = Enum.map(urls, &fetch/1)
    Task.await_many(tasks, :infinity)
  end
end

# Usage
# URLFetcher.run(["https://elixir-lang.org", "https://hex.pm"])

FAQ

Is Elixir faster than Ruby?
Significantly. Elixir runs on the BEAM VM and compiles to bytecode. Phoenix benchmarks 10-20x faster than Rails for many workloads.
Do I need to know Erlang first?
No. Elixir compiles to BEAM bytecode. You can call Erlang libraries directly from Elixir without knowing Erlang syntax.
What is Phoenix LiveView?
A library that enables real-time, server-rendered UI without writing JavaScript. State changes on the server update the DOM via WebSocket.

What’s Next

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro