Last modified: July 17, 2025
·
8 min read

Introduction to Elixir Programming - Learn Elixir Part 1

Welcome to Elixir! This is the first article in a series that will take you from complete beginner to building real applications. Elixir is a functional programming language that runs on the Erlang VM, and it's designed for building scalable, fault-tolerant applications.

What Makes Elixir Special?

Before we dive into the code, let me explain why Elixir is worth learning:

  • Functional Programming: Everything is a function, and data never changes
  • Concurrency: Built-in support for thousands of lightweight processes
  • Fault Tolerance: Applications can recover from failures automatically
  • Scalability: Designed to handle massive amounts of traffic
  • Developer Experience: Great tooling and a friendly community

Companies like Discord, Pinterest, and WhatsApp use Elixir (or Erlang) to handle millions of users. The language is particularly good for real-time applications, web services, and distributed systems.

Getting Started

Installing Elixir

The easiest way to install Elixir is through a version manager. This lets you switch between different versions easily.

On macOS with Homebrew:

brew install elixir

On Ubuntu/Debian:

# Add the Erlang Solutions repository
wget https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb
sudo dpkg -i erlang-solutions_2.0_all.deb
sudo apt-get update

# Install Elixir
sudo apt-get install esl-erlang elixir

On Windows: Download the installer from the official Elixir website.

Using asdf (recommended for all platforms):

# Install asdf first, then:
asdf plugin add erlang
asdf plugin add elixir
asdf install erlang 25.3
asdf install elixir 1.15.4
asdf global erlang 25.3
asdf global elixir 1.15.4

Verifying Your Installation

After installation, check that everything works:

elixir --version
# Should show something like:
# Erlang/OTP 25 [erts-13.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
# Elixir 1.15.4 (compiled with Erlang/OTP 25)

iex --version
# Should show the same version

Your First Elixir Code

Let's start with the interactive shell. Open your terminal and type:

iex

You should see something like:

Erlang/OTP 25 [erts-13.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.15.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

This is IEx (Interactive Elixir), your playground for experimenting with code.

Basic Expressions

Try these simple expressions:

# Basic arithmetic
1 + 2
# => 3

10 * 5
# => 50

# String operations
"Hello" <> " " <> "World"
# => "Hello World"

# Boolean operations
true and false
# => false

true or false
# => true

# Comparison
1 == 1
# => true

1 != 2
# => true

Notice that every expression returns a value. In Elixir, everything is an expression.

Variables and Assignment

# Assigning values
name = "Alice"
age = 30

# Using variables
"Hello, #{name}!"
# => "Hello, Alice!"

age + 5
# => 35

The #{variable} syntax is called string interpolation. It lets you embed expressions inside strings.

Getting Help

IEx has built-in help. Try these commands:

# Get help on functions
h String.upcase/1

# Get help on modules
h String

# Get help on operators
h ==/2

To exit IEx, press Ctrl+C

Core Language Features

Let me walk you through the key features that make Elixir work the way it does.

Immutability

Elixir's data is immutable—once a value is set, it can't be changed. If you "modify" data, you're actually creating a new value, leaving the original untouched. For example:

v = 1
asyncFunction(v) #this function uses the value of v
v = 2 #this works, value 2 is now labeled v

Although it might seem like variable v is mutable because we're reassigning it, it's actually immutable under the hood it creates a new value, leaving the original untouched. For example, if asyncFunction starts running after we reassign v=2, it will use the original value of v=1.

This immutability makes your code thread-safe.

Pattern Matching Magic

Pattern matching is one of Elixir's most powerful features. It's not just assignment—it's a way to extract data and make decisions:

# Simple assignment
x = 1

# Pattern matching with tuples
{name, age} = {"Alice", 30}
# name = "Alice", age = 30

# Pattern matching with lists
[first, second, third] = [1, 2, 3]
# first = 1, second = 2, third = 3

# Pattern matching with maps
%{name: user_name, age: user_age} = %{name: "Bob", age: 25}
# user_name = "Bob", user_age = 25

The left side is a pattern that describes the structure you expect, and the right side is the data. If they match, the variables get assigned. If they don't match, you get an error.

Atoms

Atoms are constants whose values are their names. They're like symbols in other languages:

# Creating atoms
:hello
:world
:ok
:error

# Using atoms
status = :ok
if status == :ok do
  "Everything is fine"
end

Atoms are commonly used for status codes, function names, and as keys in maps. They're very memory efficient because there's only one instance of each atom in the system.

Basic Data Types

Let's look at the different types of data you'll work with in Elixir.

Numbers

Elixir has integers and floats, and it handles large numbers automatically:

# Integers
42
123456789012345678901234567890

# Floats
3.14
1.23e-4

# Different bases
0b1010        # Binary: 10
0o777         # Octal: 511
0xFF          # Hexadecimal: 255

Notice that Elixir can handle arbitrarily large integers without any special handling. This is great for mathematical operations.

Strings

Strings in Elixir are UTF-8 encoded and support interpolation:

# Basic strings
"Hello, Elixir!"

# String interpolation
name = "Alice"
"Hello, #{name}!"

# String concatenation
"Hello" <> " " <> "World"

# Multi-line strings
"""
This is a multi-line
string that preserves
formatting
"""

The #{expression} syntax lets you embed any Elixir expression inside a string. The triple-quoted strings are called heredocs and are useful for long text.

Lists

Lists are linked lists, which means they're built from a head and tail:

# Creating lists
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", :atom, 3.14]

# List operations
[1, 2, 3] ++ [4, 5, 6]  # Concatenation
[1, 2, 3, 4] -- [2, 4]  # Subtraction
2 in [1, 2, 3]          # Membership

# Head and tail
[head | tail] = [1, 2, 3, 4, 5]
# head = 1, tail = [2, 3, 4, 5]

The | operator separates the head (first element) from the tail (rest of the list). This is crucial for recursion, which we'll cover later.

Maps

Maps are key-value stores:

# Creating maps
user = %{
  name: "Charlie",
  age: 35,
  email: "charlie@example.com"
}

# String keys
config = %{
  "host" => "localhost",
  "port" => 8080
}

# Accessing values
user.name                    # "Charlie"
user[:name]                  # "Charlie"
Map.get(user, :age)          # 35
Map.get(user, :city, "N/A")  # "N/A" (default)

# Updating maps
updated_user = %{user | age: 36}
# or
updated_user = Map.put(user, :city, "New York")

Maps are your primary data structure for representing objects or records. They're very flexible and efficient.

Tuples

Tuples are fixed-size collections:

# Creating tuples
point = {10, 20}
status = {:ok, "Success"}
error = {:error, "Something went wrong"}

# Pattern matching with tuples
{status, message} = {:ok, "Success"}
# status = :ok, message = "Success"

Tuples are commonly used for returning multiple values from functions or representing fixed-size data.

Functions

Functions are where the magic happens in Elixir. Here are the different ways to write them:

Named Functions

Functions must be defined inside modules:

defmodule Math do
  def add(a, b) do
    a + b
  end

  def multiply(a, b) do
    a * b
  end

  # One-liner functions
  def square(x), do: x * x
  def double(x), do: x * 2
end

# Using the functions
Math.add(5, 3)      # 8
Math.multiply(4, 6) # 24
Math.square(5)      # 25

The def keyword defines a public function. Functions are called using the module name, a dot, and the function name.

Anonymous Functions

Sometimes you need a function but don't want to define it in a module:

# Creating anonymous functions
add = fn a, b -> a + b end
add.(2, 3)  # 5

# Shorthand with capture operator
add = &(&1 + &2)
add.(2, 3)  # 5

# Using them with lists
numbers = [1, 2, 3, 4, 5]
Enum.map(numbers, fn x -> x * 2 end)  # [2, 4, 6, 8, 10]

Anonymous functions are useful when you need to pass a function as an argument to another function.

The Pipe Operator

The pipe operator (|>) is one of Elixir's most elegant features:

# Without pipe operator
result = String.upcase(String.trim("  hello  "))

# With pipe operator
result = "  hello  " |> String.trim() |> String.upcase()

The pipe operator takes the result of the left expression and passes it as the first argument to the function on the right. This makes your code read like a pipeline of transformations.

Sigils

Sigils are a way to create different types of literals. They start with ~ and are followed by a letter:

a = "World"
# String sigil with escaping and interpolation
~s(Hello\n#{a})
# String sigil without escaping and interpolation
~S(Hello World)

# Character list (like single quotes)
~c(Hello World)
~C(Hello World) # same as ~c but without interpolation and escaping

# Regular expressions
~r/hello/i
~R # same as ~r but without interpolation and escaping, it's deprecated

# Word lists (splits on spaces)
~w(hello world elixir)
~W(hello world elixir) # same as ~w but without interpolation and escaping

# Dates and times
~D[2023-01-15]
~T[12:30:45]
~N[2023-01-15 12:30:07]
~U[2023-01-15 12:30:07Z]

Sigils are useful for creating different types of data without having to escape characters manually.

Control Flow

Elixir has a few control structures, but you'll often use pattern matching instead:

if/else

age = 18
if age >= 18 do
  "Adult"
else
  "Minor"
end

# unless (opposite of if)
unless age < 18 do
  "Can vote"
end

case

case uses pattern matching, which makes it very powerful:

result = {:ok, "success"}

case result do
  {:ok, message} -> "Success: #{message}"
  {:error, reason} -> "Error: #{reason}"
  _ -> "Unknown result"
end

The _ pattern matches anything and is used as a catch-all.

Working with IEx

IEx is your development environment. Here are some useful commands:

# Get help
h()

# Get help on a specific function
h String.upcase/1

# Get help on a module
h String

# Reload a module after changes
recompile

# Print the value of an expression
IO.inspect([1, 2, 3])

# Pretty print
IO.inspect([1, 2, 3], pretty: true)

Creating Your First File

Let's create a simple Elixir file. Create a file called hello.exs:

# hello.exs
defmodule Hello do
  def greet(name) do
    "Hello, #{name}!"
  end

  def calculate_area(length, width) do
    length * width
  end
end

# Test the functions
IO.puts(Hello.greet("World"))
IO.puts("Area: #{Hello.calculate_area(5, 3)}")

Run it with:

elixir hello.exs

What's Next?

You've taken your first steps into Elixir! In the next article, we'll explore:

  • Pattern matching in depth
  • Working with lists and maps
  • Functions with multiple clauses
  • The Enum module for working with collections
  • Modules and structs

Practice Exercises

Try these to reinforce what you've learned:

  1. Basic Operations: Create a function that converts Celsius to Fahrenheit
  2. String Manipulation: Write a function that reverses a string
  3. Pattern Matching: Create a function that takes a tuple {name, age} and returns a greeting
  4. Lists: Write a function that finds the sum of all numbers in a list

Key Points

  • Elixir is a functional programming language built on the Erlang VM
  • Data is immutable - you create new values instead of changing existing ones
  • Pattern matching is fundamental to how you write Elixir code
  • Functions are the primary way to organize and reuse code
  • IEx is your interactive development environment
  • The pipe operator makes data transformations readable
  • Sigils provide convenient ways to create different types of literals