DEV Community

Cover image for Finite State Machines: The Most Underused Design Pattern in Frontend Development
Bishoy Bishai
Bishoy Bishai

Posted on

Finite State Machines: The Most Underused Design Pattern in Frontend Development

We've all been there. A seemingly simple UI component starts its life innocently enough. Maybe it’s a button, a form, or a multi-step wizard. As features are added, the requirements evolve, and suddenly you're juggling a dozen boolean flags: isLoading, isError, isSubmitting, isEditing.

Soon, you hit an "impossible state": a button that's both isLoading and isError. This "state spaghetti" is the #1 reason for 3 AM production bugs. It happens because we focus on what the UI looks like, rather than the rules of how it moves.

In book, Surrounded by AI, I talk about the history of tools—from the first washing machine to modern LLMs. These tools were designed to take the "boring" work off our hands so we could focus on higher-level thinking. But if we don't define the rules of our systems, even the smartest tools can't save us from chaos. Finite State Machines (FSMs) are the rules that keep our UIs from falling apart.


1. Why Boolean Flags are "Own Goals"

Imagine a simple login button.

  • What worked: It started as a simple isPending check.
  • What broke: Requirements grew. We added error, then isSuccess.
  • The Bug: A network timeout happened exactly as the user clicked "retry." Now the UI thinks it's both loading and showing an old error.

You've created a system that allows states that shouldn't exist in reality.


To fix this, we stop thinking in "flags" and start thinking in "States." A Finite State Machine says: A component can only be in ONE state at a time, and it can only move to a new state through a specific event.

Step 1: Define the States

Instead of 4 booleans, we have one status: IDLE, LOADING, SUCCESS, or ERROR.

Step 2: Set the "Rules" (The Transitions)

You can only go from LOADING to SUCCESS. You can't jump from IDLE to SUCCESS without passing through LOADING.

Step 3: Use XState

While you can use useReducer, a library like XState makes this a "perfect flow." It visualizes your logic so you can't make a mistake.

import { createMachine } from 'xstate';

const loginMachine = createMachine({
  id: 'login',
  initial: 'idle',
  states: {
    idle: { on: { SUBMIT: 'loading' } },
    loading: {
      on: {
        RESOLVE: 'success',
        REJECT: 'error'
      }
    },
    error: { on: { RETRY: 'loading' } },
    success: { type: 'final' }
  }
});

Enter fullscreen mode Exit fullscreen mode

2. The Uncomfortable Truth

The industry tells you that "Senior" means knowing the latest framework. BUT HONESTLY being a Senior means building systems that are predictable.

If a junior dev looks at your code, he shouldn't have to guess what happens if he clicks a button twice. The state machine tells him the answer before he even asks the question.


In the book Surrounded by AI, I write about Sultan Ibrahim, who was locked in a palace with no real problems to solve. He had all the resources but no capacity to lead because he never had to face the "broken" parts of reality.

When we let AI (like Copilot) write our state logic using messy boolean flags, we are Sultan Ibrahim. We are choosing the "comfortable" path that leads to fragile code. The "Human Blue Checkmark" in development is the ability to architect a system that cannot break, even when the data coming into it is messy.


3. Objections

  • "This is too much boilerplate for a button."
  • Response: It's "simple but not easy." The boilerplate saves you 4 hours of debugging a race condition later.

  • "My team doesn't know XState."

  • Response: You don't need a library. You just need a switch statement that refuses to move to an invalid state.

  • "AI can just write the if/else logic for me."

  • Response: AI is great at "The Curse of Average". It will give you the most common (and often buggiest) way to write code. You are paid to provide the "Final Touch".


4. The Unfinished Chapter

Honestly? I still struggle with where to draw the line. Sometimes I over-engineer a simple toggle into a complex machine. I'm still learning the balance between "clean" and "too much."

What about you? Have you ever shipped a "state spaghetti" bug that stayed in production for weeks? Let’s talk about it in the comments.


✨ Let's keep the conversation going!

If you found this interesting, I'd love for you to check out more of my work or just drop in to say hello.

✍️ Read more on my blog: bishoy-bishai.github.io

Let's chat on LinkedIn: linkedin.com/in/bishoybishai

📘 Curious about AI?: You can also check out my book: Surrounded by AI


Top comments (0)