Atoms are one of Elixir's fundamental data types, serving as constants whose value is their own name. Along with booleans and nil, they form the backbone of symbolic computation and control flow in Elixir. Understanding how to effectively use these types is crucial for writing idiomatic Elixir code.
Note: The examples in this article use Elixir 1.17.3. While most operations should work across different versions, some functionality might vary.
Table of Contents
- Introduction
- Understanding Atoms
- Atom Creation and Naming
- Common Use Cases
- Pattern Matching with Atoms
- Working with Booleans
- Understanding nil
- Conclusion
- Further Reading
- Next Steps
Introduction
Atoms in Elixir are constants that represent their own name. Combined with booleans (true
and false
) and nil
, they provide the foundation for control flow and state representation in Elixir applications.
Understanding Atoms
Basic Atom Concepts
# Creating atoms
iex> :hello
:hello
iex> :"hello world"
:"hello world"
# Atoms are equal to themselves
# == checks for value equality
iex> :hello == :hello
true
# Converting atoms to strings and back
iex> Atom.to_string(:hello)
"hello"
iex> String.to_atom("hello")
:hello
# Atoms represent constant values
# === checks for both value and type equality (more strict)
iex> :hello === :hello
true
Atoms vs Strings
# String comparison
iex> string1 = "hello"
"hello"
iex> string2 = "hello"
"hello"
iex> string1 == string2 # Compares content
true
# Atom comparison
iex> atom1 = :hello
:hello
iex> atom2 = :hello
:hello
iex> atom1 === atom2 # Atoms with the same name are identical
true
# Conversion between strings and atoms
iex> Atom.to_string(:hello)
"hello"
iex> String.to_atom("world")
:world
While strings can represent dynamic data, atoms are like labels that act as unique identifiers.
Analogy: If strings are like sticky notes you can write on, atoms are like permanent, engraved name tags—they are fixed and efficient.
Contexts for Atoms and Strings
Atoms and strings are often used in distinct scenarios in Elixir:
Context | Atoms | Strings |
---|---|---|
State Representation |
:ok , :error , :pending
|
Not typically used |
Configuration Keys |
:format , :option
|
Rarely used |
Dynamic User Input | Not applicable | Usernames, comments, etc. |
Pattern Matching | Frequently used | Rarely used |
Atom Creation and Naming
Naming Conventions
# Standard atoms
iex> :ok
:ok
iex> :error
:error
iex> :undefined
:undefined
# Atoms with special characters
iex> :"hello-world"
:"hello-world"
iex> :"hello_world"
:hello_world
iex> :"Hello World!"
:"Hello World!"
# Module atoms (automatically created)
iex> String
String
iex> is_atom(String)
true
Dynamic Atom Creation
# Converting strings to atoms
iex> String.to_existing_atom("error")
:error
iex> String.to_existing_atom("unknown_atom")
** (ArgumentError) errors were found at the given arguments:
* 1st argument: not an already existing atom
:erlang.binary_to_existing_atom("unknown_atom", :utf8)
# Creating new atoms from strings
iex> String.to_atom("dynamic_status")
:dynamic_status
Common Use Cases
Return Values and Status
# Common return tuples
iex> {:ok, "success"}
{:ok, "success"}
iex> {:error, "something went wrong"}
{:error, "something went wrong"}
# Pattern matching on return values
iex> result = {:ok, "success"}
case result do
{:ok, message} -> "Success: #{message}"
{:error, message} -> "Error: #{message}"
end
"Success: success"
Configuration and Options
# Option handling
# Run this code in IEx to test the functionality
defmodule Printer do
def print(text, format: format) do
case format do
:uppercase -> String.upcase(text)
:lowercase -> String.downcase(text)
_ -> text
end
end
end
iex> Printer.print("Hello", format: :uppercase)
"HELLO"
iex> Printer.print("Hello", format: :lowercase)
"hello"
State Representation
# State machine example
# Run this code in IEx to test the functionality
defmodule TaskState do
# Pattern matching on atoms to define state transitions
# Each function handles a specific state
def next_state(:pending), do: :in_progress
def next_state(:in_progress), do: :completed
def next_state(:completed), do: :archived
# Catch-all function for invalid states
def next_state(_), do: :error
end
iex> TaskState.next_state(:pending)
:in_progress
iex> TaskState.next_state(:in_progress)
:completed
Pattern Matching with Atoms
Basic Pattern Matching
# Function clauses with atoms
# Example: Handling different response states in a web API
defmodule ResponseHandler do
# inspect/1 converts any Elixir term into a readable string
def handle({:ok, data}), do: "Got data: #{inspect(data)}"
def handle({:error, reason}), do: "Error: #{reason}"
def handle(:skip), do: "Skipping"
end
iex> ResponseHandler.handle({:ok, [1, 2, 3]})
"Got data: [1, 2, 3]"
iex> ResponseHandler.handle({:error, "not found"})
"Error: not found"
iex> ResponseHandler.handle(:skip)
"Skipping"
Complex Pattern Matching
# Nested pattern matching
defmodule Temp do
def process({:user, status, data}) when status in [:active, :inactive] do
case {status, data} do
{:active, %{name: name}} -> "Active user: #{name}"
{:inactive, _} -> "Inactive user"
end
end
end
iex> Temp.process({:user, :active, %{name: "John"}})
"Active user: John"
iex> Temp.process({:user, :inactive, %{name: "John"}})
"Inactive user"
# Guards with atoms
defmodule Guard do
def check(value) when is_atom(value), do: "Got atom: #{value}"
def check(value), do: "Not an atom: #{inspect(value)}"
end
iex> Guard.check(:hello)
"Got atom: hello"
iex> Guard.check("hello")
"Not an atom: \"hello\""
Common Atom Usage in Pattern Matching
Atoms are frequently used in pattern matching to express states, results, or commands:
Pattern | Typical Use Case | Example |
---|---|---|
:ok , :error
|
Return tuples | {:ok, result} |
:pending |
State representation | TaskState.next_state(:pending) |
:true , :false
|
Boolean-like operations | if :true do ... end |
:start , :stop
|
Control commands | {:start, pid} |
Working with Booleans
In Elixir, true
and false
are actually atoms (:true
and :false
), making them consistent with Elixir's symbolic computation model.
Boolean Operations
# Basic operations
iex> true and false
false
iex> true or false
true
iex> not true
false
# Short-circuit operators
iex> false and raise("never reached")
false
iex> true or raise("never reached")
true
# Comparison operators
iex> 1 < 2
true
iex> "a" <= "b"
true
Boolean Functions
# Functions returning booleans
# Run this code in IEx to test the functionality
defmodule Validator do
# Guard clause ensures age is an integer
def valid_age?(age) when is_integer(age), do: age >= 0 and age <= 150
# Catch-all function for invalid types
def valid_age?(_), do: false
# Guard clause ensures email is a string (binary)
def valid_email?(email) when is_binary(email) do
String.contains?(email, "@") and String.contains?(email, ".")
end
def valid_email?(_), do: false
end
iex> Validator.valid_age?(25)
true
iex> Validator.valid_age?(-1)
false
iex> Validator.valid_email?("user@example.com")
true
Control Flow with Booleans
# if/else expressions
iex> if true, do: "yes", else: "no"
"yes"
# unless expression
iex> unless false, do: "executed"
"executed"
# case with boolean conditions
case {true, false} do
{true, _} -> "first is true"
{_, true} -> "second is true"
_ -> "both false"
end
"first is true"
Understanding nil
nil Basics
# nil is an atom
iex> is_atom(nil)
true
# nil is falsy
iex> if nil, do: "true", else: "false"
"false"
# Testing for nil
iex> is_nil(nil)
true
iex> is_nil(:something)
false
Working with nil
The nil
value in Elixir represents the absence of a value. It is often used to signify "nothing here" or "not applicable."
Analogy: Imagine a blank field in a form where no input was provided—that's
nil
. Just like the field exists but has no value, a variable can exist but containnil
to represent the absence of data.
Examples
# Default values with nil
# The || operator returns the first value that isn't nil or false
iex> value = nil
iex> value || "default" # Useful for setting default values
"default"
# Maybe pattern
# Why use a Maybe module?
# Helps safely handle nil values and avoid runtime errors.
# Run this code in IEx to test the functionality
defmodule Maybe do
# Implementation of Maybe/Optional pattern for safe nil handling
# If value is nil, returns nil without executing the function
def map(nil, _func), do: nil
# If there's a value, applies the function
def map(value, func), do: func.(value)
end
iex> Maybe.map(nil, &(&1 * 2))
nil
iex> Maybe.map(5, &(&1 * 2))
10
# Handling nil in data structures
iex> data = %{user: nil}
%{user: nil}
iex> get_in(data, [:user, :name])
nil
Conclusion
Atoms, booleans, and nil
are fundamental types in Elixir, forming the building blocks of symbolic computation and control flow. Through this guide, we’ve explored:
- The core behavior of atoms, booleans, and
nil
- Common ways to use these types in pattern matching and state representation
- Examples of their interaction in control flow
These basic types are essential for understanding Elixir’s functional paradigm and serve as a foundation for exploring advanced features such as concurrency, error handling, and process management.
Tip: Experiment with atoms, booleans, and
nil
in small projects or exercises to reinforce your understanding of their behavior and interactions.
Further Reading
- Elixir Getting Started - Basic Types
- Elixir Documentation - Atoms
- Erlang Documentation - Atoms
- Elixir School - Basic Types
Next Steps
In the upcoming article, we'll dive into Control Structures in Elixir:
Control Structures - If and Unless
- Understanding conditional expressions
- If and unless as expressions
- Multiple conditions and else clauses
- Guard clauses in conditionals
Top comments (2)
Loved the article! Especially the fundamental explanation of what an atom is, it’s actually challenging to explain that it’s a type that the value is its own name, and you explained it very directly and clearly! Probably one elf the best written explanations of it I’ve read! Love the examples as well, makes it easier to comprehend what this means in code.
I have 1 suggestion: I think it’s worth pointing explicitly to the reader that true and false are nothing but atoms themselves - :true and :false . An example like true = :true can perhaps illustrate this well. I think this would make your section using :true in the pattern matching clearer to understand as well for someone trying to first grasp the concept.
Overall was a really nice and entertaining read - even though I personally already knew what an atom was I still read the whole article because it was engaging and a good read. Keep up the great work ! ❤️
Thank you so much for your kind words and detailed feedback! I'm really glad you found the article clear and engaging, especially the explanation of atoms. I've just updated the article to include your excellent suggestion, adding a note that explains how true and false are atoms (:true and :false) at the beginning of the Boolean section. Thank you for taking the time to read and provide such constructive feedback, even though you were already familiar with the concepts. Your input helps make the content more complete and clearer for everyone!