Elixir Fundamentals: Functional Programming and OTP · Functional Elixir: Data, Pattern Matching and Functions

Control Flow: case, cond, if and with

Choose between case (match a value against patterns), cond (first true condition), if/unless (one boolean), and with (a happy-path pipeline of steps that each must succeed).

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

A sign-up flow that validates an email, checks the password, and creates an account, written with with so the happy path reads top to bottom and any failure stops early with a clear error.

The idea

  • case value do pattern -> ... end: match one value against patterns (with optional guards).
  • cond do condition -> ... end: the first condition that's truthy wins (use true -> as the fallback).
  • if / unless: for a single boolean. Note that if is an expression and returns a value (or nil).
  • with: chains steps that return {:ok, value}. Each <- must match; if one doesn't, with stops and returns the non-matching value (or runs the else clauses). It's Elixir's answer to nested case statements.

Only false and nil are falsy; everything else (including 0 and "") is truthy.

The code

case, cond, if and a with pipeline
defmodule Signup do
  def validate_email(email) do
    if String.match?(email, ~r/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
      do: {:ok, String.downcase(email)},
      else: {:error, :invalid_email}
  end

  def validate_password(password) do
    cond do
      String.length(password) < 8 -> {:error, :password_too_short}
      not String.match?(password, ~r/\d/) -> {:error, :password_needs_a_digit}
      true -> {:ok, password}
    end
  end

  def ensure_unique(email, taken) do
    case MapSet.member?(taken, email) do
      true -> {:error, :email_taken}
      false -> {:ok, email}
    end
  end

  def register(params, taken) do
    with {:ok, email} <- validate_email(params.email),
         {:ok, email} <- ensure_unique(email, taken),
         {:ok, _password} <- validate_password(params.password) do
      {:ok, %{email: email, id: :erlang.phash2(email, 10_000)}}
    else
      {:error, :email_taken} -> {:error, "that email already has an account"}
      {:error, reason} -> {:error, reason |> Atom.to_string() |> String.replace("_", " ")}
    end
  end
end

taken = MapSet.new(["[email protected]"])

for params <- [
      %{email: "[email protected]", password: "correct-horse-9"},
      %{email: "[email protected]", password: "whatever-123"},
      %{email: "not-an-email", password: "x"},
      %{email: "[email protected]", password: "short"},
      %{email: "[email protected]", password: "no-digits-here"}
    ] do
  case Signup.register(params, taken) do
    {:ok, user} -> IO.puts("created #{user.email}")
    {:error, message} -> IO.puts("#{params.email}: #{message}")
  end
end

IO.inspect(if(0, do: "0 is truthy", else: "0 is falsy"))

Output

created [email protected]
[email protected]: that email already has an account
not-an-email: invalid email
[email protected]: password too short
[email protected]: password needs a digit
"0 is truthy"

Run this on your computer — see “Get Started” for setup.

Common mistakes

  • Nesting case inside case for multi-step flows; with flattens them.
  • Forgetting true -> in cond, which raises when nothing matches.
  • Assuming 0 or "" is falsy. Only false and nil are.
  • A with without else that leaks raw errors to callers; translate them where it matters.

Key points

  • case for patterns, cond for conditions, if for one boolean.
  • Only false and nil are falsy.
  • with chains {:ok, _} steps and short-circuits on the first failure.
  • Use else in with to turn internal errors into friendly ones.

Further reading: case, cond, and if, with.

Practice questions

When would you use `with` in Elixir?

Answer

For a sequence of steps that each return {:ok, value} (or similar) where any failure should stop the flow: with chains the matches and returns the first non-matching value, or handles it in an else block, avoiding nested case statements.

Which values are falsy in Elixir?

Answer

Only false and nil. Everything else, including 0, empty strings and empty lists, is truthy.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?