Skip to main content
Erlang, despite being a powerful language for building concurrent and distributed systems, has several common anti-patterns that can lead to performance issues, maintainability problems, and bugs. Here are the most important anti-patterns to avoid when writing Erlang code.
Don’t use processes for simple operations that don’t need concurrency, state isolation, or fault tolerance. Processes have overhead and should be used when their benefits are needed.
Use OTP behaviors like gen_server, supervisor, and application instead of reinventing them. They provide battle-tested solutions for common patterns.
Avoid using ++ for list concatenation in loops. Instead, use list prepending ([Head | Tail]) and reverse the result at the end if order matters.
Leverage Erlang’s pattern matching for cleaner, more declarative code. Use it in function heads and case statements.
Use binaries instead of lists for string operations. Binaries are more efficient for string manipulation in Erlang.
Use records or maps for structured data instead of raw tuples. They provide named fields and better pattern matching.
Use proper error handling with tagged tuples ({ok, Result} and {error, Reason}) and propagate errors up the call stack.
Avoid using try/catch for control flow. In Erlang, it’s more idiomatic to use pattern matching, guard clauses, and tagged tuples for error handling.
Use proper supervision trees to manage process lifecycles and handle failures. This is a core strength of the Erlang VM.
Always use timeouts with receive to prevent processes from blocking indefinitely if the expected message never arrives.
Use ETS (Erlang Term Storage) tables for shared state when appropriate, especially for read-heavy scenarios or when low-latency access is required.
Organize your code into cohesive modules with clear responsibilities. Follow the principle of single responsibility and create a logical hierarchy of modules.
Write comprehensive documentation for your modules and functions using EDoc comments. Include examples, parameter descriptions, and return value information.
Use Dialyzer and type specifications to catch type errors at compile time and improve code documentation.
Follow the OTP application structure with proper application, supervisor, and worker modules.
Register important long-lived processes with names for easier access. This is especially important for system processes that need to be accessed from multiple places.
Include the sender’s PID in messages that require a response. This allows the receiving process to reply to the correct sender.
Use list comprehensions or lists:map for transforming lists. lists:foreach is only for side effects and doesn’t return a useful value.
Use proper testing frameworks like EUnit or Common Test instead of writing manual test code.