Skip to main content
Elm, despite being designed to prevent many common programming errors, still has several anti-patterns that can lead to maintainability issues, performance problems, and code that doesn’t follow the language’s idioms. Here are the most important anti-patterns to avoid when writing Elm code.
Avoid excessive use of Maybe.withDefault. While it’s convenient for simple cases, using case expressions provides more control and clarity, especially when dealing with multiple fields from the same Maybe value.
Avoid using strings to represent a fixed set of values. Use custom types instead, which provide type safety, better documentation, and exhaustive pattern matching.
Avoid deeply nested record updates, which are verbose and error-prone. Instead, create helper functions that focus on updating specific parts of your data structure.
Don’t use List for everything. Use appropriate data structures like Dict for lookups, Set for unique collections, and Array for indexed access. This improves performance and makes your code more expressive.
Avoid overusing flags for application configuration. Flags should be used for essential startup information, but excessive flags make initialization complex and brittle. Consider loading configuration from a server or using reasonable defaults.
Always follow The Elm Architecture. Don’t try to work around it by storing state outside the model, using ports for things that could be modeled in Elm, or ignoring the Cmd part of the update function.
Always include type annotations for top-level functions. Type annotations serve as documentation, help catch errors earlier, and make refactoring safer.
Avoid excessive nesting of HTML elements. Break your view into smaller, focused functions to improve readability and maintainability.
Use opaque types to represent domain concepts instead of type aliases. Opaque types provide better type safety and prevent accidental misuse of values.
Avoid excessive use of tuples, especially for domain concepts with more than two values. Use records instead, which provide named fields and better documentation.
Use custom types for messages instead of strings or tuples. Custom types provide better type safety, documentation, and exhaustive pattern matching.
Organize your code into modules based on functionality. This improves maintainability, compilation times, and allows for better encapsulation of implementation details.
Use Maybe for optional values and Result for operations that can fail. These types make your code more expressive and force you to handle all possible cases.
Use Elm’s JSON decoders properly. Don’t try to manually parse JSON; instead, build composable decoders using the decoder library. This approach is more maintainable and handles errors better.
Use Elm’s built-in subscriptions for things like time, animation frames, and keyboard events. Only use ports for functionality that truly can’t be handled within Elm.
Use the Elm debugger and time-traveling debugger during development instead of littering your code with Debug.log statements. For production, ensure all debug statements are removed.
Write tests for your Elm code. Elm’s type system catches many errors, but tests are still important for verifying business logic, edge cases, and integration between components.
Follow Elm’s formatting conventions by using elm-format. Consistent formatting makes your code more readable and helps avoid trivial discussions about style in code reviews.