Skip to content

Modus-Logo-Long-BlackCreated with Sketch.

  • Services
  • Work
  • Blog
  • Resources

    OUR RESOURCES

    Innovation Podcast

    Explore transformative innovation with industry leaders.

    Guides & Playbooks

    Implement leading digital innovation with our strategic guides.

    Practical guide to building an effective AI strategy
  • Who we are

    Our story

    Learn about our values, vision, and commitment to client success.

    Open Source

    Discover how we contribute to and benefit from the global open source ecosystem.

    Careers

    Join our dynamic team and shape the future of digital transformation.

    How we built our unique culture
  • Let's talk
  • EN
  • FR

State Machines in Production

Published on September 2, 2020
Last Updated on October 8, 2021
Application Development

Whenever we make a technical decision in a software implementation project, we’re making a trade-off. As software engineers, we are used to leaning on our knowledge and experience to help us fight uncertainty in these kinds of decisions. Of course, learning from other people’s experience can help as well and that’s what this blog post is all about. In this article we’ll share what you should know about state machines before deciding to use them in your next project.

New Mental Model

Picking the right tool for the job can take us a long way, but that works the other way as well. A tool that is not a good fit for your team can introduce unnecessary friction and cause long term problems. We found that a good way to evaluate if state machines can help you is by comparing their mental model to Redux.
Redux Diagram
Redux mental model

From a very high level perspective, Redux does this: given the current state and an action, you get the new state of the application. In this case, state includes both the data the application is operating on and the data that describes the current application status. This is the key difference – state machines do not treat those as the same: finite states (like loading) describe the current application status while extended state represents the data the application is operating on.

FSM Diagram
State machine mental model

State machines work on a similar principle: given the current state and an event, we transition to a new state and run effects that allow us to update the extended state. This serves as a powerful tool for codifying business rules: all the possible application states and their relationships are declared upfront so the application can only be in a state you defined. The effects are then used to perform changes to the extended state. Check this article out for more details.

This gives us a lot of control over application state, but it also comes with a cost. In the following section we’ll share some of the implementation challenges you might encounter when implementing state machines.


NEW RESEARCH: LEARN HOW DECISION-MAKERS ARE PRIORITIZING DIGITAL INITIATIVES IN 2024.

Get Report


Who Owns the State?

Previously we mentioned that state machines operate on finite and extended states. While finite state is an integral part of a state machine definition, extended state is optional. So that leaves us with a decision to make – where do we store the data that the application is operating on? Should it be stored and managed by the state machine internally? Or should it be external, with some way of accessing it both from the state machine runtime and our UI components?

Our library of choice for dealing with state machines is XState. XState ships with a powerful API that uses context to store data within the state machine and manage its lifecycle. There are two strong benefits of using this approach:

  • Context is part of the state machine definition so it serves as a strong contract for all downstream consumers. This works especially well if types are used to specify the context interface.
  • Context is safe from accidental mutations: it can only be modified from effects that happen on state transitions.

This approach works well for a lot of cases, but it can be challenging to manage at scale.

Problems with Scaling

As requirements get more complex, a state machine definition can grow in two ways: by introducing new states and transitions or by making the extended state grow. Let’s talk about scaling states and transitions first.

The easiest way to model and visualize behavior is to keep the state machine flat – all possible states are mutually exclusive in that case. This can eventually create a maintenance problem that’s so bad that it has a name – combinatorial explosion. You can recognise that is happening if, when adding a new state, you end up modifying multiple existing states and the transitions between them – that means it’s time for a new approach.

XState gives us a strong tool to fight this kind of complexity – statecharts. This is something we use extensively since it’s a natural way to break complex behaviours down. We prefer thinking about statecharts as bits of reusable behaviour you can embed in your higher level state machines.

The other scaling problem manifests itself when the extended state grows so much that unrelated data updates cause frequent re-rendering. If you keep all the data in XState context, that means that all the components that are consuming the context will render again on every change. This is especially painful if the data model is nested – components will render again even if the specific subset of context they are interested in hasn’t changed. If this affects performance, it can be mitigated by careful use of React.memo. We also leveraged selector functions to make sure our derived data is calculated only when the underlying raw data changes.

Using Selectors
Using selector functions with XState

If you have a complex data model, this might be an issue and an alternative reactivity solution would be preferable. Dedicated reactivity solutions can help a lot in that case, so you might opt for something like MobX alongside Xstate. Leveraging its reactivity model would help making sure that components render only when relevant data changes.

Conclusion

So far we touched on the higher level benefits and challenges of using state machines to control application state. In addition to that, here’s a list of pros and cons coming from the hands-on experience with state machines in production, pros first:

  • Business rules and user interactions can be described in a declarative way, switching focus from coding to business requirements.
  • The Visualization tool can be used to turn state machine configurations to statechart diagrams, which helps communication between developers and product team members.
  • Debugging is easier: you could see a history of states and events that triggered the transitions.
  • Components can be leaner as a side effect of switching to a declarative coding style.

And then the cons:

  • Modelling real world problems with state machines takes time and practice to do well. All team members needed some time to learn the approach and get comfortable with it.
  • XState API has a large surface area and the documentation can be overwhelming for beginners.
  • Simple coding errors in state machine configuration often led to bugs that were hard to track down
  • Since there are no clear recipes on how to reduce complexity when your model grows, you have to do it yourself and it can be a bit painful
  • While improving readability on a higher level, some low level coding tasks get a bit more complex

If you’re thinking about using XState or explicit state machines in general, I would recommend experimenting on a smaller scale first. Regardless of how sound the concept is, putting it into practice is not trivial and it takes time and effort to be done the right way. Instead of going all the way and managing the full application state with state machines, my suggestion would be to first try it in scope of a single component. That’s a nice isolated level you can use as a proof of concept without interfering with the rest of the application. If you find value in that, you just might be ready to drive more complex behaviours using state machines.

Posted in Application Development
Share this

Ivan Kovic

Ivan Kovic is a Full Stack Engineer at Modus Create. He enjoys learning different programming languages and paradigms, with a special passion for data modelling and application architecture.
Follow

Related Posts

  • Native ES6 in Development, Transform in Production with Babel 6
    ES5 in Production, ES6 in Development

    Many modern browsers already ship with ES2015 support, especially our favorite development browsers such as…

  • The State of HTML5 Gaming
    The State of HTML5 Gaming

    Browser based Games Not that long ago, the idea of playing video games in your…

Want more insights to fuel your innovation efforts?

Sign up to receive our monthly newsletter and exclusive content about digital transformation and product development.

What we do

Our services
AI and data
Product development
Design and UX
IT modernization
Platform and MLOps
Developer experience
Security

Our partners
Atlassian
AWS
GitHub
Other partners

Who we are

Our story
Careers
Open source

Our work

Our case studies

Our resources

Blog
Innovation podcast
Guides & playbooks

Connect with us

Get monthly insights on AI adoption

© 2025 Modus Create, LLC

Privacy PolicySitemap
Scroll To Top
  • Services
  • Work
  • Blog
  • Resources
    • Innovation Podcast
    • Guides & Playbooks
  • Who we are
    • Our story
    • Careers
  • Let’s talk
  • EN
  • FR