Atom

Atoms in Elixir are constants that are represented by their name. They’re similar to symbols in other languages and start with a :.

They are extensively used to label or categorize values. For example, when a function might fail, it often returns a tuple tagged with an atom such as {:ok, value} or {:error, message}.

iex> :red
:red
iex> :blue
:blue
iex> is_atom(:blue) (1)
true
1 The function is_atom() checks whether a value is an atom.

While atoms can be written in snake_case or CamelCase, snake_case is commonly used within the Elixir community. Ensure your atoms are descriptive and indicative of their purpose for code readability.

It’s worth noting that while atoms are handy, they aren’t garbage collected and consume system memory until the system is shut down, so they should be used sparingly. Do not dynamically create atoms from user input or untrusted data, as this can exhaust your system’s available memory and cause it to crash. It is unlikely that you run into this problem but not impossilbe.[1]
Agentic Coding Tip: Never Let the Agent Call String.to_atom/1 on User Input

The WARNING above is the single most important rule an AI agent needs to be reminded of when writing Elixir. When asked to "look up the user’s role from a request parameter," Claude will often reach for the shortest path:

# DoS waiting to happen
role = String.to_atom(params["role"])

Every new unique value of params["role"] creates a new atom, and atoms are never garbage collected. An attacker who can reach the endpoint can send unique strings until the BEAM runs out of atom slots (the default limit is around one million) and the entire node crashes. Phoenix has this trap built into many request parameters.

The safe call is String.to_existing_atom/1, which only succeeds for atoms that already exist in the system:

role = String.to_existing_atom(params["role"])
# ArgumentError if the string isn't already an atom

Even better, pattern-match the expected set explicitly:

case params["role"] do
  "admin"  -> :admin
  "editor" -> :editor
  "viewer" -> :viewer
  _        -> :viewer
end

Rule to add to your project’s CLAUDE.md:

Never call `String.to_atom/1` on data that comes from
outside the application (HTTP params, request bodies,
database rows, message queues, environment variables,
external APIs). That is a denial-of-service primitive.
Use `String.to_existing_atom/1` when the set of valid
atoms is already defined at compile time, or convert
explicitly via a `case` expression enumerating the
allowed strings. The same applies to
`List.to_atom/1` and `:erlang.binary_to_atom/2` with
`:utf8` encoding.