Introduction

If you’re a React developer, chances are you’ve felt the framework’s power and complexity. Hooks are incredibly flexible, TypeScript catches many errors—yet runtime surprises still slip through. useEffect dependencies that never feel quite right. Performance bugs requiring strategic React.memo and useCallback. These aren’t signs you’re doing React wrong; they’re the inherent trade-offs of React’s flexibility.

Some of my most-read posts are the deep-dive React articles—especially on React.memo and reconciliation. A few are used in React courses, and some have even been translated to Korean(!). This popularity reflects a truth: once you step beyond the basics, React’s complexity increases dramatically and the good can become challenging faster than you expect.

Elm takes a very different approach. Elm isn’t a UI library. It’s a small language with one way to build applications, designed for reliability and clarity. With Elm, you trade flexibility for focus. There’s exactly one architecture (The Elm Architecture, or TEA), exactly one way to represent side effects, and a type system that works relentlessly to prevent runtime errors. The result is a developer experience that feels—at least at first—strangely quiet. You don’t spend hours debugging why your component didn’t re-render or why state mutated unexpectedly. Instead, you spend time modeling your problem domain.

For React developers, the first encounter with Elm can feel almost too simple. There are no hooks to juggle, no contexts to configure, no class vs. function components. Just a Model, an Update function, and a View. And yet, that simplicity scales to codebases with hundreds of thousands of lines.

So why consider Elm if you already know React?

  • To catch more errors at compile time. Many React/TypeScript errors show up only in the browser. Elm’s compiler is famously strict: if it compiles, it’s very likely to work.
  • To simplify mental overhead. Fewer concepts mean fewer ways to get things wrong. Instead of choosing between a dozen state management solutions, Elm gives you one—TEA.
  • Because it’s actually fun. Many developers describe Elm as enjoyable to work with. The compiler points you toward fixes, the language encourages clear modeling, and your app tends to just work once it compiles.

But there’s a deeper reason to learn Elm, even if you never use it professionally: it’s the fastest way to truly learn functional programming. Not watered-down FP, not FP-flavored JavaScript patterns, but real, uncompromising functional programming—the kind that changes how you think about code in any language. Haskell has too many language features and quirks, making it notoriously hard to get started with something practical. Elm, by contrast, is remarkably small and focused—the entire language fits in your head. And crucially, you’re working in a domain you already know: building web UIs. This combination of strict FP discipline and immediate practical application makes Elm uniquely effective as a learning tool. If you’ve ever wanted to “level up” as a developer by learning functional programming, Elm is your shortest path. Especially if you’re already familiar with React.

This book is not about convincing you to abandon React. Instead, it’s a guided tour for React developers: what Elm looks like, how it compares, and how you might start using Elm—even if only as a single widget inside a React app. By the end, you should have a feel for whether Elm can play a role in your work. And, perhaps even more importantly: you will have learned functional programming along the way!

If React has been your main frontend toolkit, think of Elm as a chance to see what frontend development feels like with different trade-offs. It may not be for every project—but once you’ve experienced the calm of Elm’s compiler, you may find yourself wishing more of your codebases worked this way.

And, quite frankly, learning Elm will be worthwhile whether you end up using it professionally or not.

To give you a taste of what I mean, let me show you the kind of guarantees Elm provides. Throughout this book, you’ll see what I call “Elm Hooks” in each chapter—compelling previews of what makes Elm delightful for that particular topic. React has hooks for state management, but Elm has hooks that keep you hooked on the language itself.

Let’s try one right away. It’ll demonstrate two of Elm’s main selling points: the friendly compiler and the strict type system (or, good cop / bad cop, if you prefer).

An icon of a info-circle1

Elm Hook

Given the following Elm code

 1 type Animal = Cat | Dog | Penguin
 2 
 3 
 4 animalToString : Animal -> String
 5 animalToString animal =
 6     case animal of
 7         Dog ->
 8             "dog"
 9 
10         Cat ->
11             "cat"
12 
13         -- Ooops, forgot about that Penguin!

The compiler answers the following:

 1 -- MISSING PATTERNS --------------- /path-to-your/src/Main.elm
 2 This `case` does not have branches for all possibilities:
 3 
 4 306|>    case animal of
 5 307|>        Dog ->
 6 308|>            "dog"
 7 309|>
 8 310|>        Cat ->
 9 311|>            "cat"
10 
11 Missing possibilities include:
12 
13     Penguin
14 
15 I would have to crash if I saw one of those. Add branches for them!
16 
17 Hint: If you want to write the code for each branch later, use `Debug.todo` as a
18 placeholder. Read <https://elm-lang.org/0.19.1/missing-patterns> for more
19 guidance on this workflow.

Sure beats all those ensureNever helpers you’ve written in TypeScript, huh?