# Simplifying Complex UIs with Finite Automata & Statecharts

Canonical: https://davidk.dev/talks/simplifying-complex-uis-with-finite-automata-statecharts/
Video: https://www.youtube.com/watch?v=RqTxtOXcv8Y
Speaker: David Khourshid (@DavidKPiano), creator of XState
Type: Conference talk · Event: JSConf Iceland 2018 · Year: 2018 · Duration: 34:28 · Views: 20042 (as of 2026-10-19)
Topics: Statecharts, State machines, Visual modeling

## Summary
My talk is simplifying, though I would have liked to see a talk titled simplifying complex talk titles with fewer words. When we're developing user interfaces for 1.5 billion people, we have to imagine all the different ways a user could use the user interface.

## Key takeaways (quoted from the transcript)
- I like JSON better than blackbox functions because you can enumerate all the possible states and actions and be able to transition from one to the other with nice, simple function right here. (12:00)
- What I did was a couple months back, I refactored this part of the app so that it uses an explicit state machine just like this. (15:00)
- If I'm trying to search for something, and the user didn't give me a search query, then I'm not going to transition to the next state. (21:00)
- You could imagine how this would simplify just really complex state machines. (25:00)
- By using statecharts, they were able to generate just all of these integration tests where they could test each and every one, and ensure that every single state was being hit, and every single possible action or event was being handled. (29:00)

## Chapters
- 0:00 Introduction
- 3:00 When we're developing user interfaces for 1.5 billion people…
- 5:00 You might know this as spaghetti code…
- 10:00 If I were to search for iceland� let's search for goats…
- 14:00 If we want a user to, let's say, share a session…
- 25:00 We could simplify this by just nesting them in a single…
- 27:00 People do use statecharts in order to model their applications

## Transcript
[0:00] Thank you. Is this on? Can you all hear me? All right. There we go! So, yeah. My talk is simplifying, though I would have liked to see a talk titled simplifying complex talk titles with fewer words. I know what you might be thinking right now. The React API, a little scary? There's a good book in order to understand how to best work with it, and it's doing whatever Danny says to do.

[0:00] The second half of this talk is exactly what I'm talking about today, except in the more general sense. I want to talk about it, though. Not in the context of React or you or Angular or any of the last frameworks that came out last week. But I want to talk about it in a frameworkless context. This is an idea that doesn't just apply to any framework or no framework in general, but really any programming language as well.

[1:00] We can see how it could help us simplify our UIs. So I'm a pianist, and I have been playing for about 20 years. This is the approach that I take to software development. I made the analogy that if software is music, then developers are composers. We're taking that music, and we're composing it in a way that is applicable to any language or any platform. And we do so in a variety of ways.

[1:00] However, music's interesting, because with music, you have music notation, which you just saw there. Let's go back and see it. So this stuff right here. This music notation is hundreds and hundreds of years old. However, the exact same notation is used today to represent music from classical, romantic, whatever the hell�Bjork is. You can give a musician who understands how to read sheet music, and they will be able to play it. And this works for all instruments, and you know, all musicians of all levels.

[2:00] So I was wondering, what is the music notation of user interfaces? What do all user interfaces have in common, and how can we represent that declaratively? So when we're working with software, we usually deal with APIs. Good APIs have three main qualities. They're well documented.

[3:00] You can read the documentation and understand exactly what's happening with the API. They are very predictable. The API is going to work the exact same way every time. And they're also testable. You can test against an API. They provide mocks for you or have a test database or something like that. So user interfaces, though? User interfaces are really complex. I mean, does anyone find developing user interfaces easy? Those of you watching online, no one raised their hand, which is good, because I would have called them out if they did.

[3:00] We have to deal with something called a human API. That's all of you, all of our clients, everyone we work with. Human API is undocumented or unpredictable, and we're untestable. And we're also extremely buggy. If you don't believe me, turn on an American news network, and you'll see proof of that. So I work for Microsoft. By the way, I'm not relating Microsoft to any of this terrible API stuff, I promise. And at Microsoft, we have more than 50,000 software engineers throughout the world, and at least 7 designers, I'm sure. [ Laughter ] and we also serve one and a half billion customers.

[4:00] I think this is just for windows. It might be more than that. When we're developing user interfaces for 1.5 billion people, we have to imagine all the different ways a user could use the user interface. And you know, this is different cultures and languages. You wonder why diversity is so important in tech? We have to reach the 1.5 billion people. So we don't develop software for just one person. However, when we're developing software with just the happy path in mind, that's essentially what we're doing.

[5:00] We're assuming that there's this ideal user who's using our applications in the exact way that we want them to.

[5:00] But this never happens. We're going to take a look at the code example, but I don't want you to read too deeply into the code. I just want to see if you relate to this. We're going to create a sample F where we query the flickr API and then the results. Similar to what Dan was doing with movies in his last talk. So it's pretty simple right? We do a fetch. Then when we get the data, we show it.

[5:00] Are there any bugs here? Are we done? Well... Let's say the data takes a long time to load. Okay. So now we have to set this loading flag, loading is true. And once we finish loading data, then we have to set loading to false. Are we done? No. What if we have an error. Well, we have to hide the loading screen, show the error, right? But, are we home free yet? No.

[6:00] Because, we also have to make sure that we clear the error once we try to search again. As you can see, the complexity is getting bigger and bigger. Now my onserve to vent handler is getting extremely huge. I'm sure many of you can relate to this code. Many of you will say yeah, some of my code looks like that. Sure. What if your boss comes to you and says we want the use tore be able to cancel the search, because, you know, sometimes it takes too long.

[6:00] Sometimes they want to search for something else. That logic gets crazier. I don't know if you can read that. I'll zoom in for you. But there's a lot of complexity in just one event handler. You might know this as spaghetti code, where our code quickly gets unmanageable. Some of you might see your engineers and think I don't do spaghetti code. All of my code is modular and layered and separation of concerns.

[7:00] You might not write spaghetti code, but you might write lasagne code, which is spaghetti code in layers. This is called the bottomup approach. That approach is whenever you have an event like onclick or on change, you do all of the logic in that event. And that can refer to so many different actions, and some of those actions might change the states. But you have to choose which actions are executed, otherwise you might edit the state in a wrong way, and you have to ad if statements.

[7:00] There's a lot of downsides to this. This bottomup code is difficult to test. All the application logic lives in your mind. So when you're writing your tests, you have to output it from the developer's mind on to the tests. And it makes it really hard to work with other developers. The code is difficult to understand. The code that I just showed you for showing and hiding the gallery, if you were to try to parse that as a brandnew developer working on the project, you would find it really difficult to do so.

[8:00] The code will contain bugs, for sure, because there's going to be edge cases that you might not have considered. It will also be difficult to enhance, just like we've seen with cancellation. And features, such as cancellation, make the code work. They explode the potential possible states and actions that could happen in your application. Here's an intuition for moving forward. We have a realization that UI components are not independent. We have frameworks that they isolate components and say these components fit in their nice little box, and we can stick them in the page anywhere.

[9:00] Everything they see on the screen is in the exact same context, and we have to coordinate from components to component. So another realization is that actions, which is what happens when you do an event, are based both on the events executed, and the current state of the context. And it's also that this event action paradigm is too simple to represent all the possible states our app can be in. So, what's the solution?

[9:00] Standard statecharts. I know a lot of you may have heard these before or studied them in school. Even though you might know what these are, you probably haven't applied them to user interfaces and actual software.

[10:00] So, looking up material for this could be a little complicated. I'm a visual person, and I'm going to do it in a very visual way. First you have an initial state. Then you have a finite number of states, which represents the states that the app could be in. When an app is in one state, it can't be in the other state at the same time. You have a finite number of events, such as fetching, resolving, or rejecting a promise.

[10:00] You have a mapping from state to state based on these events. So for example, when you're fetching a promise and it resolves, you go to the fulfilled state. And same way with reject and rejected state. And you have a finite number of final states. User interfaces can be continuous. We could keep using them. It doesn't say we're done, so go uninstall Instagram because you're not going to need it. If you go to this site, it's going to say promises are just a state machine.

[11:00] That should give you a big clue with what we're working with here. So the original app that I showed you, the gallery app can be modelled as the state machine right here. So we start in the idle state, and then we go to search. Then we're in the searching state. If we try to search again, it loops back. It's like hold on. We're already searching. We can either resolve or reject the search results.

[11:00] And from those two states, we could search again. This is clearly seen by the arrows going from success or failure back to searching again. So, the machine that� the state machine that I envision looks sort of like this. It's a JSON object. I like JSON better than blackbox functions because you can enumerate all the possible states and actions and be able to transition from one to the other with nice, simple function right here.

[12:00] For example, if we're in the searching state, and I get a resolve, then I just look it up in this JSON object. This searching, on resolve, success. In TypeScript, it's a record. I guess it's the same in C#. In PHP, they call for help. I don't know. So here's an example of what I just showed you. If I were to search for iceland� let's search for goats first. Let's say I don't want to search for goats.

[12:00] I'll just cancel that. Let's search for Iceland instead. When we do the event of selecting a photo, we have a state transition that goes from gallery to photo. We can exit that, search again, cancel, search. Let's cancel that. Iceland. And so you see it goes from state to state, and no matter how many times I try to break this app, it's not going to deviate from this state machine that I have here.

[13:00] And you see there was a few extra things such as actions over here. When I'm done searching, update the items? We're going to discuss that in a little further detail. And also this is part of an article that I wrote which gives an introduction to finite state machines. So I use visual studio share.

[14:00] I work on it and I also have to use it. It's called dog fooding. Anyway... and this is sort of what it looks like. And so, I don't know if we have Sarah and Burke in here. Here's Burke right now. You can think of it as Google docs for collaborative coding. It's in preview in Microsoft. And I have no control over what they're typing, and they're typing this in realtime. With this live share, we can collaboratively code in the exact same document at the same time.

[14:00] These are a complex series of events that are happening at the same time, and so we have to manage all of these possible events and all the possible states that this could be in. I have no idea... Oh God. Good luck breaking the demo. It probably will, though. Ha ha. Anyway! I'm going to end this before it gets too crazy... One of the big things I have to work on in liveshare is this whole signin flow.

[15:00] You would think that signing in is easy and, you know, signing in, signing out, all of that. But it's actually not. Once you start drawing it out, it becomes a lot more complex. So for example, there could be a signin failure. You know, especially from Iceland. I'm so glad that worked in Iceland. All of our servers are based in the United states. So when you have a signin success, then you go to the signin state.

[15:00] From there you could go do two things. You could either share a session or join a session. So what I did was a couple months back, I refactored this part of the app so that it uses an explicit state machine just like this. We were getting a lot of bugs. We were saying what if we're in the middle of sharing and the user signs out? What if we join the session, and then we decide to share a session at the same time?

[16:00] That shouldn't be allowed. We had all of these if/else and switch statements, so I decided there has to be a better way. I did the simple JSON object approach. The state machine that I set up? It was very easy to add telemetry to it. I added telemetry, and I get this nice visual showing exactly the frequency of how users go from state to state. So you could see obviously, most of the users go through this initializing and then signing in state, and then you could either externally sign in, or you could sign out.

[16:00] And then you could see that users tend to go to either joining or sharing, you know? At about the same frequency. And yeah. So it clearly shows, too, how many users go to the error state and how many users go to the success state for each of these states as well. Now, these are very powerful insights. Because while telemetry will only tell you an atomic bit of data, here is telemetry that tells you the entire user flow of what's happening.

[17:00] We could limit this to user and be able to see a nice path of what the user actually did in the app. So it's sort of like, you know, like the funnel diagrams, except even crazier. We could also use state machines for integration testing. And so this gets a little bit into graph theory, so bear with me a little bit. Here we would be using a shortest path algorithm. You could choose your favorite one.

[17:00] It sounds like an Icelandic name. Any of these formulas for finding the shortest path. And so, for those who don't know, the shortest path is just going from one state to the other with the minimal cost. So you could imagine each of these arrows have a wait. And so you just want to minimize, you know, how long it takes to get from one path to the other. Sort of like in Google maps, finding the shortest ETA from one point to the other.

[18:00] So analytics would provide these waits. And this represents all the happy paths a user could take. If we want a user to, let's say, share a session, and get to the shared state, we would imagine the user going from signed in to sharing to shared. Just that simple. These could be automatically generated from the JSON structure just like this. But, remember, we're not just dealing with the happy paths. So, we would do a death first search algorithm to find all the other paths a user could take.

[19:00] Some of you might be thinking these algorithm things, I didn't think I would need them. You sort of do; they're really helpful. And so, these represent all the possible user flows. You can imagine a user getting from state A to state C might not take the easiest route. They might take a longer route to get all the way to C. And it reveals all the edge cases. When you model every single possible state with every single possible action, and you get all of these flows, you're going to basically have 100,000% test coverage for anything that could possibly happen in your app.

[19:00] And of course these could all be automatically generated. I caution against this, because you would be generating integration tests� it would generate a lot of integration tests, which isn't necessarily a bad thing, but when you're doing continuous integration, you don't want your tests to take four hours long. It makes software bugs visually clear by drawing a map and putting them on the diagram. You can see when I do this event, I'm going to the wrong state.

[20:00] Or maybe I have an unhandled event, which does nothing. Or maybe I have a missing transition. That's shown here, too. For example, Dan was showing the example where when you click a movie, it just waits and waits, and then you get the next movie, and there's no loading state. You can clearly see that in the state transition diagram. And also, my favorite, race conditions. These are just made completely nil and void with state machines, because you explicitly say, when I get this event, I go to this state.

[20:00] If I get another event after that I don't care about, it's not going to cause a race condition, because I will ignore that event. And that's what state machines can help with. Now I want to talk about Harold statecharts. This could go on for hours and hours, because there's so much information here. I'm going to give an overview of what these are and how these extend state machines in order to improve the way we look at user interfaces.

[21:00] Because, user interfaces can get complex, and finite state machines might not solve everything. So with statecharts, they are similar to state machines. They are an extension of it. Except we can have actions with Harel statecharts, too. They actually do things to the state and the app. We can have guards which prevent states from going from one to the other if something doesn't match the condition. If I'm trying to search for something, and the user didn't give me a search query, then I'm not going to transition to the next state.

[22:00] That could be declaratively modelled as well. There's the idea of nested states. This is parallel states. It is not concurrent states where an app can be in two states at the same time. It's more a way of simplifying the number of states you have so your state machine doesn't look all crazy. And also, history, which is a very common feature that many users would want in order to go back to their safe state.

[22:00] So I created this library called XState which models finite state machines and statecharts in a declarative way, so that all you would have to do is use this JSON structure to model your state machine, and then you would get the next state by calling transition on the current state and the action. It's functional, and it's going to work in any framework or no framework at all. It also has the notion of actions, so it will return you an object telling you, these are the next actions that you're going to take once you transition to a state.

[23:00] It also has guards, so you could pass in some external state, and it will validate against that external state and say, Okay, this condition is met. So you go on to the next state. It also has hierarchy so you can represent and compose machines easily. It has orthogonality so you can represent parallel states and also represent remembered states. If you're doing a payment flow and you're asking for cash or card, the user can switch between them, and the user gets to the review page.

[23:00] You want to remember if the user chose cash or card. So you go to method.history, and it would remember that. And this is a purely functional, no side effect type of thing. And so you can imagine the applications of where you could apply this. One example of where you would use orthoganal states is this example. Say you have bold, italic, or underline. A user can use all or none of them, and it's represented by all of these eight states.

[24:00] And you can imagine if we add more options such as align left, align center, justify, it's going to explode into even more states. With orthogonal statecharts, we just have this succinct representation of these possible states, so that we know we can go from bold on to after, italics on to off, and same way with underline, and we can represent all of those at the same time. And also, hierarchy helps to reduce the number of arrows that you have in your finite state machine.

[25:00] So for example, both the success and failure states, we could search again and go back to the searching state.

[25:00] We could simplify this by just nesting them in a single state, the search state. This is fully supported by XState, and yeah. So, you could imagine how this would simplify just really complex state machines. So, I released XState about six months ago at React rally, and since then, there has been a lot of development and a lot of people actually using it to make projects such as React automa and others. And pretty soon, I'm going to be releasing another version, minor version.

[25:00] Don't worry; nothing crazy, of XState where we improved the user ergonomics. It's full SCXML. It's not sexy. Code is not sexy at all. A reactive interpreter, visualization interpreter, and a lot more examples. In order to show you what I'm talking about, here is an example of a visualization tool. That same JSON object that you passed into XState and the same one I used for my gallery app, this is one example of where you would use this.

[26:00] My initial state is start, and I can hover over this search action, and go to this state. Then I could see the possible different actions� sorry, events that I could take from this state. So let's say I have a search success. And so, I go into the gallery view. And then I choose to search again, or I could select a photo.

[27:00] So, I can visually see what's happening in my state machine by pasting in that JSON object. That's hard to do with just a function, so that's why I decided to do it in a more declarative way. I'm going to be releasing this soon. I want you all to enjoy the rest of the conference before I release it, so you can have time to play with it afterwards. That's my excuse, at least.

[27:00] It's not out yet. Now, it would be, you know, sort of remiss of me to not talk about sort of the downsides of state machines and statecharts, but the truth is that you all are already creating finite state machines and statecharts in your app already, and you don't even know it. However, the ones that you're creating are implicit. They're full of bugs. They're incomplete. And that's just the nature of how we code.

[28:00] So the advantage of finite state machines and modeling them in a very explicit way is that you reduce a lot of complexity up front just by getting rid of your if/else statements and getting rid of your switch statements and having a direct mapping. However, that quickly explodes into many, many different states. So statecharts help manage that with a little higher complexity cost up front. But in the long run, you will see it will be very easy to add features, remove features, and handle different actions and different possible modes of your application.

[28:00] So, of course, there's a lot of advantages to using statecharts, and the ones I am going to quote are not actually from me. They're from NASA. A few years ago, NASA had to send this little vehicle right here all the way to Mars. And they did so using statecharts. So the advantages that they said was, first of all, there's visualized modeling. So you could both model and examine the model of a statechart using visual tools.

[29:00] You get these precise diagrams that tell you exactly what's going to happen when certain conditions are met, when you're in a certain state, and this event happens. When you're sending something to Mars, you can't exactly just send it there and hope that it works, right? You have to be absolutely sure that your billion dollar project is going to handle every single possible state. And so they also use statecharts for automatic code generation.

[29:00] I'm not a fan, but you could see how it could be beneficial. Also, comprehensive test coverage. By using statecharts, they were able to generate just all of these integration tests where they could test each and every one, and ensure that every single state was being hit, and every single possible action or event was being handled. Also, you know, being on a big project, you have a lot of requirement changes. We want to add this to the rover.

[30:00] We want to remove this functionality. We want to change the way this works. And when you're working on a big project, and you have these late breaking requirement changes, then things can get really stressful. With statecharts, since you could clearly see the full flow of your application, you could easily add and remove features without worrying what's going to break, what's going to change, what's going to be affected. So there are some disadvantages as well.

[30:00] And the disadvantage is that my screen just went black. I guess that means there are no disadvantages. Ha ha. Anyway... Since the rest of my slides are forked, I want to show you this real quick. I gave a talk about one and a half years ago. It was at JSConf Iceland. This is where I saw the primary use case for using state machines and statecharts. There was this really complex UI design that I really wanted to model, and I found it pretty difficult without using an explicit state machine or statechart.

[31:00] So what I'm doing here, there's three different states. This one. And there's different actions as well. So the user could pan right, but just a little bit. So I handle that as a transition from going to state A, back to state A if they didn't move far enough. Of course, if they move far enough, it goes to the next state successfully. And also, it handles, like, if I'm going up, and I decide to go left and right, it's not going to do anything, because we're already going up and down.

[32:00] So it knows when this event happens, don't try to go to a different state, because we're already transitioning to one state. I also wanted to mention two very good resources. The first one is on spectrum.oi/statecharts. This is a community where we talk about statecharts, their possible applications, and how they're used everywhere. Statecharts are nothing new. They're invented by David Harel in 1987, which is what? 30 years ago? This is not cuttingedge stuff.

[33:00] If people are asking why aren't we using statecharts everywhere now? Well, the thing is, they are in embedded systems or systems programming. People do use statecharts in order to model their applications. We don't, quite yet, but I think it's about time that we catch up to it. And so, Eric Mogansen made a wonderful resource called the unfinished world of statecharts. Over here, if you want to learn more information, there's a well wealth of information here.

[33:00] I really want to encourage you to revisit your application and the way you think about things, and actually try to visualize things. You will see that by visualizing your application, all of its possible states, all the events that could happen, that you're going to have a clearer representation of the possible bugs, the possible features that you might have, the user improvements, and everything else. So I want to thank you all for having me.

[34:00] Unfortunately, I can't show you my last slide, because it's not working, but thank you. [ applause ]
