How to Fix the Elixir Error: Erlang Error Guard_expr

While experimenting in IEx, I stumbled upon a confusing error that didn’t make sense at first. I wrote what I thought was harmless code:

case %{} do
  x when x == %{} -> true
  _x -> false
end

And then boom

** (ErlangError) erlang error: :guard_expr

At first, I assumed I messed up my syntax. But no this error was deeper. So I decided to dig in and what I found taught me something fundamental about how Elixir guards work.

Why This Error Happens

The root cause is surprisingly simple:

Elixir guards only allow a limited set of expressions.
x == %{} inside a when clause is NOT allowed, because you can’t compare against a map in guards.

Guards only allow comparisons with simple values like x == 1 or x == nil, but map literals (%{}) and complex data types are forbidden.

So Elixir raises:

erlang error: :guard_expr“Invalid expression inside a guard.”

Correct Version

Instead of forcing %{} inside a guard, I can directly match it as a pattern:

case %{} do
  %{} -> true
  _ -> false
end

Why does this work

Because pattern matching is allowed, even with complex structures like maps.
But guards are more strict, and %{} is not allowed inside them.

My Next Experiment A Custom || Macro

I wanted to be clever and build my own version of the || operator one where empty lists ([]), empty tuples ({}), and empty maps (%{}) also behaved like false.

So I tried:

defmodule Or do
  defmacro left || right do
    quote do
      case unquote(left) do
        x when x in [false, nil] or x == [] or x == {} or x == %{} ->
          unquote(right)
        x ->
          x
      end
    end
  end
end

Guess what? SAME ERROR. Removing or x == %{} suddenly made it work.

At that point, I knew %{} is illegal inside guards. Period.

Final Fix My Working Custom || Macro

The solution? Avoid guards entirely. Use raw pattern matching instead.

defmodule Or do
  defmacro left || right do
    quote do
      case unquote(left) do
        false -> unquote(right)
        nil -> unquote(right)
        [] -> unquote(right)
        {} -> unquote(right)
        %{} -> unquote(right)
        x -> x
      end
    end
  end
end

Now it behaves almost exactly like ||, except with extra falsy values:

import Or

[] || "fallback"      #=> "fallback"
%{} || "default"      #=> "default"
5 || "ignored"        #=> 5

Practice Challenges

Try extending your skills:

  1. Write a truthy?/1 function that returns true or false based on custom falsy rules (false, nil, [], {}, %{}).
  2. Modify the macro to also treat "" (empty string) as falsey.
  3. Write an && macro equivalent that mirrors the same logic.

Final Thought

Related blog posts