Skip to content

Elixir Control Flow — if, unless, cond, case, and with

DodaTech Updated 2026-06-29 2 min read

In this tutorial, you will learn about Elixir Control Flow. We cover key concepts, practical examples, and best practices to help you master this topic.

Elixir has fewer control flow constructs than most languages — but each one is more powerful. Because pattern matching is everywhere, you'll reach for case and with more often than if.

In this tutorial, you'll learn every control flow tool in Elixir and when to use each one.

What You'll Learn

  • if and unless
  • cond for multiple conditions
  • case for pattern matching
  • with for pipelined matching
  • Truthiness and falsiness

if and unless

# if/else (returns a value — everything is an expression)
age = 20
status = if age >= 18, do: "adult", else: "minor"
IO.puts(status)  # => adult

# Block form
if age >= 18 do
  IO.puts("Welcome!")
else
  IO.puts("Access denied")
end

# unless (opposite of if)
unless age < 18 do
  IO.puts("Adult")  # Runs when condition is false
end

# Only nil and false are falsy — everything else is truthy
if 0, do: IO.puts("truthy")      # Runs (0 IS truthy!)
if "", do: IO.puts("also truthy") # Runs
if nil, do: IO.puts("never")      # Doesn't run

cond

Use cond when you have multiple conditions (replaces else if chains):

defmodule Grade do
  def letter(score) do
    cond do
      score >= 90 -> "A"
      score >= 80 -> "B"
      score >= 70 -> "C"
      score >= 60 -> "D"
      true -> "F"  # Default (always matches)
    end
  end
end

IO.puts(Grade.letter(85))  # => B

case

defmodule HTTP do
  def handle_response(status_code) do
    case status_code do
      200 -> "OK"
      201 -> "Created"
      301 -> "Moved Permanently"
      400 -> "Bad Request"
      401 -> "Unauthorized"
      403 -> "Forbidden"
      404 -> "Not Found"
      500 -> "Internal Server Error"
      code when code >= 400 -> "Client/Server Error"
      _ -> "Unknown"
    end
  end
end

IO.puts(HTTP.handle_response(404))  # => Not Found

with — Pipeline for Pattern Matches

defmodule Ecommerce do
  def checkout(cart_id, user_id) do
    with {:ok, cart} <- fetch_cart(cart_id),
         true <- cart.items != [],
         {:ok, _} <- charge_user(user_id, cart.total),
         {:ok, order} <- create_order(user_id, cart) do
      {:ok, order}
    else
      {:error, :not_found} -> {:error, "Cart not found"}
      false -> {:error, "Cart is empty"}
      {:error, reason} -> {:error, reason}
    end
  end

  defp fetch_cart(id), do: {:ok, %{items: [:a], total: 100}}
  defp charge_user(_, _), do: {:ok, :charged}
  defp create_order(_, _), do: {:ok, :order_created}
end

IO.inspect(Ecommerce.checkout(1, 1))

Practice Questions

  1. Write a function using cond that returns a temperature description for 5 ranges.

  2. Use case to pattern match on different shapes of data: tuples, maps, lists.

  3. Write a checkout pipeline using with that validates stock, charges, and ships.

  4. Find a case where unless reads more naturally than if and write it.

  5. Use cond with a true default clause to implement a priority-based system.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro