Make Your Apps Resilient Using Finite State Machines
Watch on YouTube (Kent C. Dodds)
What is this talk about?
In this episode, David Khourshid gives the rundown on how finite state machines can make your app more testable, more resilient to bugs, and easier to refactor. (From the video description.)
“The main idea behind finite state machines is that you are in only one of a finite number of states at any given time.”
“In a nutshell, that's all finite state machines are, and of course, there's an initial state.”
From the opening of the conversation.
Key takeaways
- Two years ago was pretty much the first time I talked about finite state machines in general at a conference, and so I was really happy to, you know, be able to do that. 1:00
- The main idea behind finite state machines is that you are in only one of a finite number of states at any given time. 5:00
- When I'm building, a finite state machine with XState, for example, I'm going to take my, you know, my object. 10:00
- With the actor model, it's like, think of each of your components having local state, and that's it. 21:00
- Is to say, like, take the feature that you're working on right now, and, take a step back and see if you can model it in your head as a finite state machine. 29:00
Quoted from the transcript. Conversations include other speakers.
Full transcript
From YouTube auto-captions. Punctuation and capitals were restored automatically. Technical terms were normalized; some errors remain. 5,318 words.
0:00 Introduction
HE Hey, everyone, it is your friend Kent SE dods here, and I'm joined by my friend Dave Corid. Hey, how you doing good, good, hey, so I say that you're my friend. But I now I have to ask, do you go by Dave or David? David? There's a special type of person who's a Dave, and I'm not that type of person. I, I don't even know what that type of person is, but I'm mostly David. Cool, cool, my, uh. My brother's name is David, and I grew up always calling him David. But then when he got married, his in-laws and that family started calling him Dave. And now everybody in my family is calling him Dave now too, which is kind of weird, but yeah, I'm either David or DK, DK, all right, Donkey Kong, Oh, nice, nice or piano man, you might actually, yeah, yeah, cool, well, so David, was it like two years ago that you spoke at React Rally about XState?
Was that been that long? It was 2 years ago? Yeah, I went to Rack Rally last year, but just to attend, which was a great time. But two years ago was pretty much the first time I talked about finite state machines in general at a conference, and so I was really happy to, you know, be able to do that. At React to Rally, yeah, that's that I. I remember that talk, because of course, I was there. I, I never miss. If I can snag a ticket, which is not easy, but yeah, by the way, React Rally is my favorite conference. So those listening, try to get that ticket. It's so good. Yeah, but yeah. So I remember that talk, I was really impressed, I thought it was super cool. That, like, conceptually I, I still have yet to actually build something with XState. But every time I think about managing complex state it, I always go back to XState as probably the like, the best solution.
I need to test that theory, but that's that's what I'm working with right now. So so, yeah, let's talk about more than just XState, let's talk about the general idea of what a finite state machine really is.
2:00 I realized, like when I studied music in college…
And I'm kind of curious. How you got into all this, too? So why don't we start there? I like to kind of start with the why behind the topic. So what, what made you interested in this? And, and why do you think, other people should dedicate some of their brain cycles to it? So it's going to sound weird, but it actually stems from my musical background. You know, if you play an instrument, or anyone who plays an instrument, really, they know that there's, there's this. There's music notation that you would read either from sheet music or maybe guitar tabs, or like a lead sheet or whatever. That basically tells you in a very visual way how something is going to be performed. And I realized, like when I studied music in college, we had to study so many different genres and different types of music. But they all use the same music notation, and, this music notation was expressive enough to be able to, inform the player how to play these many, many different styles of music.
And so, as I grew in my development career, I was thinking, what if there was some sort of music? Notation? For logic, like application, logic, application, behavior, things that change over time, and, of course, naively. At first I tried to make my own, but then I discovered there's this really old computer science concept called state machines. And, it was presented in a very theoretical sense. But I tried to dig deeper and think about how it could be used in user interfaces and in applications. And things that weren't strictly theoretical or strictly academic. And, so that's when I stumbled into statecharts and I realized that people have been asking the same questions that I've been asking for the last 30 or so years. And some people, much, much smarter than me, have already, you know, developed this idea of statecharts and using, state machines and statecharts and user interfaces.
4:00 Finite machines are these finite states…
And other types of applications like embedded electronics and, you know, aircraft control, all of these advanced use cases. And then I started seeing them everywhere, and I think that might be where, like I, I've heard a lot of people, you included, you know, just seeing these state machines in. The software rewrite, and then we stop and think about, like, oh, you know, uh? This could be expressed as a finite series of states instead of just random booleans. So, dude, that's really interesting, you know, there, there really is nothing, new. Under the Sun, but it. It's cool that it kind of, came out of, your, your thoughts around music notation. So, um. Finite state machines, I think maybe they're like, we can understand each of those terms. In isolation, but together maybe, we could get a more clear definition. So can you help us understand what exactly is a finite state machine?
Sure, so I could give you the Wikipedia definition, or I could just give you a little example. Let's take a promise. This is something that we work with every day as job, script or front end engineers. And even in the back end, a promise starts off as like, once the promise is initiated. It's pending, it's waiting for a result that might or might not come over time, and then, while we're waiting for that pending promise. It goes into a state of either success or failure, which is it's either resolved or rejected. And so those are finite, discreet states that we could say that our promises in. And so the main idea behind finite state machines is that you are in only one of a finite number of states at any given time. So a promise cannot be both resolved and rejected, that makes absolutely no sense, it's impossible, nor could be pending and, rejected, you know, at the same time.
And the things that cause these changes in states are events, because it doesn't just happen for no reason, everything happens based on an event. And so, finite machines are these finite states, these finite number of events that either a user could do or some other external system could do. And how these states change from one state to another based on those events. And in a nutshell, that's all finite state machines are, and of course, there's an initial state. You have to start from somewhere. Yeah, that makes a lot of sense. So, the like, conceptually, we can think of many examples, like, you know, stop lights is, I think, probably pretty. You know, worldwide understood example but, to like. In code, this is represented by you have your object, and that object may have certain number of properties. Like, if we were to take your promise example, you know, it would have the, you know, resolved or rejected.
Like, there's the response and there's the error if you're doing a fetch call or something like that, so it would be, like, there could be multiple properties in our state, but we could only have one set of those properties in our state at a time. Is that like, am I kind of going off on the Reals there, or is that kind of? Does that make any sense? What I just said, let me let me take a step back. So, like, maybe with the a good thing to define is what state even is, and I, I define it as something. It's it's data or a value that changes ES over time. And so like you have static data, like we have, you know, data. JSON in your project or whatever, it's just static data.
8:00 Finite state machines can represent the finite
And that could be like the users's, you know, first name and last name, that maybe that'll, you know, never change in the life lifetime of our application anyway. And that would be just data. But then there's state, which could be represented by, you know a, the position of the mouse on the page or something like that. That something that can change over time is, and so that's, that would be state, and that's something that could be represented in a finite state machine, right? Yeah, so you could think about state in two ways there's the discret finite states, and there's sort of continuous or infinite states. And so if you think about a cup of water, the water could have maybe a 100 Mig of water, or, two cups of water or three quarts of water. Something like that. Like just an infinitely large number of different measurements that you could, have in that cup of water. But you could also separate it into distinct categories of the cup is empty, the cup is filling, or the cup is full.
And, so finite state machines can represent the finite. States of that, where, you know, you could have empty, filling or full and, uh. With extended by night state machines or statecharts, which is an extension that David Herold made in 1987. I pretty sure that was like the year before I was born, man. Oh wow, yeah, uh. There's the notion of extended states, or INX state, it's called context. But this, this idea that we're separating finite state from this infinitely, you know, many values of state. So, for example, your first name, name, your last name, my, you know, date of birth. Just things that could be very, very numerous. And we can't really express all possible combinations of that. And so, in the statecharts, view, state is a mixture of this finite state and this infinite state. If that, if that makes sense, that kind of makes sense. So, like when? When I'm building, a finite state machine with XState, for example, I'm going to take my, you know, my object.
That represent all of the states, all the finite states, and I'm going to feed that into my state machine with XState. But then there are like, dynamic pieces of data or information. And so I. That was actually a question I wanted to ask about, during our chat was, how do you U represent those kinds of pieces of information where, maybe the database? Like there's some sort of, bad code on the back end. And so my data is in a like, corrupt situation where, like, it is an impossible state or something like that, so we can't really represent that upfront. Anyway, like, as I'm writing out my state machine, configuration or whatever. So like, where do those two places meet? When I'm, like, in a typical practical application that I'm building with something like GState. So you're talking about, like, if you want to represent something, finite states, but the infinite state might be corrupt or something.
Well, yeah, that may have been a little tangential. I'm mostly interested in, and like I, I do have those two different things where I. I know all of the states and I know what the values should be, you know, given my promises in the resolve. State, I know what these values should be, or, or whatever the case may be. I can represent the state of my component, based off of this type of event or whatever. But then we have the dynamic data that's coming from the server. So where do those two things, mesh in a, like, a practical real world scenario? Okay, so this is sort of the difference between Redux and XState, is that in XState you get back this date object, which it has a value, which is your finite date. As in, let's take a promise, for example, it's either idle loading success or failure, right?
Those are the four possible states it could be in. And when it's in success, you have this data, which of course, could be anything. It's whatever payload comes from that, and you know failure, of course, you know whatever error message comes from that as well. And so it combines the two in this, in this object, and, you know what? When I talk about this, I actually keep thinking about the stopwatch example that you made with hooks like you and Dan were going back and forth between, this stopwatch example. And the one thing I noticed. And so that this or speaks to the practical aspect of this, is that. In the stopwatch example, there was. There was no notion of, of stopped or playing, or reset, or any of these discreet events, it was just, um. And this is even in Dan's example, when he tried to show you the right way. I, I mean, of course, his way is correct, it works, it works.
Yeah, so technically correct, um. But it was trying to determine state and to, set state based on those infinite values, for example, when you pressed reset, all it said was like, Oh yeah. Set the set the current time to zero and set the play rates to zero. But those aren't finite states, we're deriving finite states from that, but it's not really explicit, and so XState. If I were to rewrite a stopwatch example in XState. Not only would I have some part of the state saying here's the current time, I would have a part of the state saying it's. Playing. Or it's paused, or it's reset, or, you know, something like that. Yeah, I, I actually, I love that idea. I'm I'm totally going to, to rebuild the stopwatch with XState. Now to figure out. Because I've been like thinking, I need to find a good example or a good use case or scenario.
For me, stopwatch would be a perfect example. Yeah, yeah, yeah. And and then I'll, once I figured that out, I'll I'll ask you about it, and then I'll probably do a live stream or something. Explaining how how works? That'd be pretty cool. I love that. Yeah, cool. So, we do. We do have these. Like in the stopwatch example, you have that lapse time that can be calculated from the time that the stop stopwatch was started. U minus the current time, or the current time minus that. And so, like, our statement machine would only represent the finite states like it is running, it is stopped, it is, now when? When it's paused. That state needs to live somewhere as well. What do you have when you're representing this finite state machine? Though? Those actual values are not something that you know ahead of time. Like, I don't know when the start time is going to be, and I don't know when the pause time is going to be.
So those wouldn't be stored in my, in my state machine configuration object. I don't know what you call that, but th those values wouldn't be stored there. So do I separate my finite states from this, more ephemeral? Like, you know, these values that I'm going to find out in the future, or so. So you don't necessarily separate them. Where they live in the states is in those two pockets. So the value, which is the finite, sorry, the finite state, and the context, which is your more infin. And so those come back in the same object, so inherently they're together. And so what you define in your state machine is, let's say you. You have a start event where it tells you the current time that you started, or you have a lapse events where it says, here's how long. You know, the lap was, or, you know, stop. Event telling you what time it stopped? Those events carry data so they could be, and Redux does the same thing.
You have a discrete type for the events, such as starts, lap, reset, stop, and you also have these potentially infinite values, so you know, start time, the person's name, something like that. And so you would represent that in the state machine as well, like assigning those values to the context or the infinite state. And that actually prevents a lot of bugs that we don't realize our presence in so many of our applications.
17:00 With the actor model, it's like…
Like, you've worked with Redux a lot? I'm sure. Yeah, sort of. I. Yeah, I. I worked with it a little bit a couple years ago, long enough for me to realize that it wasn't my thing. So I haven't done a lot of Redex, but I, I definitely understand it. So we can chat about Redux, right? So, and I'm sure many listeners are familiar with Redux, or at least the concepts behind Redux, too. And so something I see very often is that we're basing what the next state is just on the event, not on the current state. If it is a current state and they really need to use a current state. They might add an if statement, like, if this is this state, then, you know, do this as well. And so if you think about, like a simple loading example where you're trying to load async data. And you do it based on events where it's like, I have a success event with some data, put it in there, and then change this and change that, that not adequate, because what if I ask you, can this success event be done without me even loading, without me even going to the loading states?
And the developer might say, no, that's that would never happen. And then I would go and I would tell them, can you prove it? Can you? I like, if we put $100 on the table, you know, guarantee me that will never happen. And then they would say, like, Oh well, I mean, the button is disabled, but yeah, it's like, there's, there's no, yeah, there's no formal way, there's no guarantee that, you know, it's impossible for, you know? That's to happen, so any events can happen at any time and change data at any time. And that increases the surface area of application behavior that, you know, we really wants to narrow down, and, you know, make impossible states impossible. Oh, that makes so much sense. And that, and that, leads into the idea that, like, um. The finite state represents not only the state that it could be in, but also the states that it can travel to from that state.
Exactly, yeah, which is a very important concept. Yeah, that makes a lot of sense. So, and actually that leads into a great, discussion I wanted to get into as well. Is, um? Redux is typically used for, like, entire application state. And I'm curious about XState and where it plays in. With that, with that kind of thing is, would I be able to use XState or some other finite state machine? Implementation? Am I able to use this concept for my application state? Is it typically better for a single component state? Where? Where is the best application of this kind of thing? Like, would I use it? Here's where I use my component state and that I have Redux for my application state. Like, where? Where's the best practical scenario there? So that's. That's also one of the big differences between Redux and XState, or just using finite state machines, and it's that Redux embodies this whole Global nature.
There's one single atomic state. Events can happen from anywhere. It updates this date. And then you use a bunch of fancy selectors and memoization to make sure that updating the Global state doesn't make everything fire off and change. Because that would be a performance. Yeah, we're, and we're experiencing that with hooks. Because you can't, you know, exit early. Yeah, exactly, um. So XState is sort of a different idea. XState really embodies the idea of local state. Now, of course, you could have like an XState, like a state machine at the very top, and then state machines for each one of your components. And then they could send events to each other. And, by having this distributed local state, you have more of what's actually known as the actor model, which is also an A. It's actually an older idea than statecharts. So the actor model, which, I know this is going on in Tangent, but I promise it's related, that's fine.
No, let's go into the actor model, yeah. Actor model is mainly used for concurrency among distributed systems, but it could also be used in user interfaces to simplify how different components work together. So, a real quick explanation, let's say you're an actor, an actor. The way that you're we're communicating with each other is the way that actors communicate with each other. Or sending events to each other, like, I'm sending you an event with my voice, you're sending me an event back with your voice. And you know, we change our internal state based on those events now. The way we code is sort of antithetical to that. It's more like we could read each other's minds, and we could hope that, you know, I, I could read your mind or something. But with the actor model, it's like, think of each of your components having local state, and that's it. And the way components change their state with relation to other components is they communicate with each other based on events now in React.
We sort of do that naturally, without thinking like when we have on change, on whatever, on disabled, just basically all of these event handler props. That's a way of parents component communicating with a child component, or a child commun component communicating with a parents component. And so to answer your question, it doesn't matter whether you use Redux or XState or whatever, it's just, uh. The idea that you do need local state and local state is not something that should be avoided. And I think that was a thing that Redux might have unintentionally encouraged. Yeah, and I. I wrote a form library called React Redux form, which I don't really maintain any more. I just tell people to go to Formic and I accept PRS and I review them and everything, but it was a form library based on Redux. And I found that people were having very significant performance issues once they had forms with maybe 50, 70, 100 fields.
And this is very easy to Envision, like if you make some sort of spread app or an app where? You have many line items, things like that, you could easily get to that many, that many inputs and that many components. And so I had to had to make some sort of makeshift local React Redux form for each component, which it worked. But it sort of felt like going against the grain with Redu, so that's why local components data is great. Yeah, I'm, I'm definitely a big fan of that. So what with, if I recall correctly, with XState? I can, like, have nested statecharts, right? Like, so I, I could buil, like, just like I have a nested component tree, I could have, these NSA charts. Where, oh, you know what? Like in React. If if you need two components to communicate with each other, their siblings, you're going to lift. State. What is that?
The same concept? When I'm using XState that I'm going to say I have these two places that need to communicate state, so I'm going to lift that state to this, you know, statechart above. Or how does that work? Sort of. So what you're describing would be more of having a parents dat chart, where two components just communicate with that parents and that parent communicates back with the two components. Right now, nested state machines, it's more for refining states. So let's say that we have a loading applic. Or like a, yeah, like a loading application where you want to fetch some data, but it might be fetching multiple parts of data. So you would have three states, let's say idle loading and success, but that loading state, you might want to refine more, like load, user load, group load, friends load, MEdAD.
25:00 The problem is that with integration tests…
Like just all these substates of that main state. And so it's just a way of refining state and grouping states in a logical way. That prevents you from having to create so many different states and transitions. HM Interes, that's what its purpose is. Cool, Okay, cool. So, we're we're coming down on our time. But I did want to ask you about a couple other things. Because I happen to know that you are doing some really interesting things with testing. And finite state machines, that have nothing to do with whether your application is actually using XState or. Finite state machines. But are is actually this concept of modelbased testing? Can you tell us a little bit about that? Yeah, so, actually just going to your article. You wrote an article a while ago about the testing trophy and it is an important point because it shows that. Unit tests, you could write a 100, a thousand unit tests.
But your application could still have serious flaws, whether it's in behavior or edge cases, that you might not catch in unit testing. But the problem is that with integration tests, even though integration and end tests are extremely important, and they actually test, how your application is going to behave in real life. These tests are difficult to author, like just authoring one takes a chunk of time. And then for every single other possible thing your user could do, you would have to author another one. And then, if application functionality changes, then you have to rewrite or refactor every single one of those integration tests. And I think that's one of the reasons that people tend to avoid writing too many integration tests, which, of course, makes your application's reliability suffer. So there's this other old concept, I'm I'm all about old concepts, but that there's this old concept called modelbased testing, and so what it is. You would describe your application as a state machine, even in a vague way.
It doesn't matter if your application is written with a state machine or not. You would just say my application behaves like this. It starts in this date, it moves to this date, and so on. And so you describe your application as this, and then you write Heris Sixs, saying, when my application is in this loading state, there's a spinner available. And so, if the spinner is on the screen, then just assume it's on the loading states. And so. You would write all of these, all these Heris sixs, and you would also write things that say, the action of click, the action of submitting the form is physically going to the sub submit button and clicking it. And so with the combination of this, you could take your state machine, and you could trace every single path from, like the start states, to any other state. And you, you generate hundreds.
And maybe thousands of different test plans, and each of those test plans says, in order to get to this date, you have to go here, do this, go here, do that. So it's sort of like Google Maps. Long story short, it's a way for you to automatically generate integration and end to end tests without having to write any of them by hand. Which, yeah, it's pretty cool. Yeah, yeah, I think. And and the key element there is the fact that the state machine, encodes the transitions. That you can go from one state to another. And that's what enables that. Kind of power, right, right, exactly. And so you could use the same test plans. I call them for writing both integration and endtoend tests. Too. Cool. Yeah, yeah, that makes a lot of sense. U where integration? Maybe you mock out the backend HTTP calls, but then end to end, just let them happen. Exactly, yeah, it's that's a very interesting and, cool concept.
And and the fact that, and I think maybe this is a really good takeaway for people, or a call to action for people. Is to say, like, take the feature that you're working on right now, and, take a step back and see if you can model it in your head as a finite state machine. Where? Okay? So the user enters my form or, or they enter my, you know, the checkout cart or whatever. U, maybe it's a checkout flow. And then they transition from that into the enter their payment information. And then if they enter it wrong, then they go into a state of error. And I feel like that exercise could really help. Not only in, you, identifying potential bugs. Like, oh, I, I didn't think about what, what could happen if they, hit the back button here or something, you know, right, yeah, and then it.
It can also have the benefit of you thinking, Hey, maybe I could apply, you know, some finite state machine, solution to this, situation, so that I avoid those kinds of bugs, right? And even if not, if you're just this is something I tell everyone. It's like, if you don't need to use my library, don't use it, take a pencil, and. Paper draw out each state and each arrow between the states, and you're going to quickly realize. I forgot to add that I didn't consider this edge case, and more just by thinking of your application as a finite state machine. Yeah, I think that's, that's super great advice and a good thing to kind of end on here. Yeah, like, as you're drawing that out, I'm just picturing this in my mind, like you, you're drawing arrows, connecting this different states. And then you realize, Oh, actually. I think in my application, it's right now possible to transition from this state to that one, and that should not be possible.
So I got to go fix that bug, right? Yeah, very cool. So is there anything else that you'd like to, to just give to our audience?
31:00 Spectrum is fantastic by the way
To take away from this conversation? Any other, calls to action or anything? Well, if you want, please check out my XState library. I, uh. By the time of publishing this, there might even be a new version with new features and some extra functionality around testing, analytics, simulation and more. And I, I'm working on it constantly, and there's also some really great communities around it, especially on spectrum. chat charts and, yeah, cool, cool, all right, we'll get people on that. Spectrum. Spectrum is fantastic by the way. Just love that, very cool. And also, just as a teaser for people to go to XState, doj..gov. Just refresh the page to watch that animation again. So yeah, cool, David, thank you so much for taking the time to chat with me about this. And one last question, where can people find you on the internet? I'm on the internet everywhere. At @DavidKPiano, so piano, the instrument I play, K, because my last name's too hard to spell.
Cool, all right, sounds good. That is, yeah, that's it for this episode. Thanks everybody, and we'll be in your ears next time. Bye, all right, thanks.
Related talks
-
Build Better Apps with State Machines
Learn With Jason (CodeTV), 2021. 1:23:12, 7.4K views -
Introduction to statecharts
React Finland Mini-Conference #2 (2020). 44:07, 1.3K views -
The Visual Future of Reactive Applications with Statecharts
HolyJS, 2019. 1:01:09, 5.8K views -
Building State Machines Using XState (Modern Web Podcast)
Modern Web Podcast S07E20, 2021. 47:21