State Machine, From a Diagram to Code

Terrick Mansur
Dec 2, 2020 · 5 min read

In this article, I will be showing you four simple steps you can use, to convert a state machine diagram into working code. These steps will make writing state machines so easy, that you will want to have all your stories described in state machine diagrams.

Image for post
Image for post
Photo by Yogesh Pedamkar on Unsplash

Before we get into the solution, let's understand what a state machine is. A state machine is a system that takes in events and produces new states given the accumulated events that it has received.

This diagram is a very common state machine that we find in most applications.

Image for post
Image for post

It describes fours states the system can be in; “Not started”, “Loading”, “Error”, “Succes”, and fours event that the system can receive “Fetch”, “Refresh”, “Error”, “Success”.

Let’s start going through the four steps to convert this into working code.

Step 1:

For the first step, all we need to do is create an enum and list all the events as cases. If you look at the diagram, the events are the arrows. As you can see, each arrow has a name. These names will be cases in our enum. The example diagram has 4 different event kinds arrow, “Fetch”, “Refresh”, “Error”, “Success”. Let’s put then in an enum.

Step 2:

The state enum is as easy to create as the event enum . All you need to do it list the states and create an enum with acasefor each of the listed state. States are represented as the bubbles in a diagram, we have four states, "Not Started", "Loading", "Error", "Success". Let's create our enum.

Step 3:

A "reducer" is just a fancy word for a function that takes in a state (current state), an event and returns a state (new state).

With this information, we can already declare the function signature.

func reducer(currentState: State, event: Event) -> State { ... }

Before we write the implementation, let's look at the diagram again. Specifically how the arrows are drawn, where they originate from, and where they point to. This will guide us when we are implementing the reducer. Let's look at the "Success" arrow.

Image for post
Image for post

The "Success" arrow originates from the state "Loading" and points to the state "Success". In code terms, we can say, if the current state is "Loading" and we receive a "Success" event, then update the current state to "Success". Let's write this in code.

if currentState == .loading && event == .success {
newState = .success
}

Let's look at our next arrow, "Fetch". "Fetch" originates from state "Not Started" and point to state "Loading". Let's write the if statement for the “Fetch” arrow.

if currentState == .notStarted && event == .fetch {
newState = .loading
}

If we do this for each of the arrows we would end up with the five if statements below (the numbers of arrows there are in the diagram).

if currentState == .loading && event == .success {
newState = .success
}
if currentState == .notStarted && event == .fetch {
newState = .loading
}
if currentState == .loading && event == .error {
newState = .error
}
if currentState == .error && event == .refresh {
newState = .loading
}
if currentState == .success && event == .refresh {
newState = .loading
}

I think it’s safe to say that anyone with little coding experience can see here that the last fours if statements can be made into else if.

However, for languages that support switches with multiple parameters, using a switch would be an even better solution. Since Swift supports this, and Swift is the language I know the best, I will be converting these if statements into a switch and adding it to the reducer function which we defined the signature for earlier.

You might have noticed, that we had added default in our switch. The switch will only hit the default when there is a case that is not defined by the diagram. Some of these cases might not even exist, an example would if an error event occur when we are in state notStarted. This can never happen, because how can we get an error if nothing has happened yet? In these cases we just return the current state passed in, meaning no change to the state.

Step 4:
Put it all together

At this point, we have everything we need to start using our code. Your reducer works and will give you the correct results. The logic is written and the code is valid. But let's put it all into a nice class/struct.

In our struct, we have our two enums Event and State as sub-types. We have our reducer function as a static private method (the reducer can also just be passed in), we have an update function that takes in an Event, and we have a currentState variable with a private set, so only the struct can change it.

Once you instantiate this state machine, there is only one way to interface with it, and that is by calling the update functions. Other then that, you can only read the currentState. Let’s write the unit test to see how it works.

It works! That's it, your state machine is done.

Pro tip:
Passing values with your events.

In most cases both your events and states will have values. For example, the error event will most likely have an “error message” and your error state most likely also have a userMessage(the message you will show to your user).

To add these values to our states and events, all we need to do is add associated values for our cases. Here is how that would look if we were to add all the values for our Event and State enums.

Although the values are not indicated in our example diagram, you can include them by writing “Error: String” as the label for the arrow, and “Error: String” as the label for the state.

Closing remarks:
Anyone can draw a state machine.

Developers sometimes shy away from writing state machines. The term sounds like a fancy/complicated system. But in reality, it’s very simple. The biggest benefit to following these steps in my opinion is that anyone can write a state diagram, a designer, a product owner, or a client, and with these steps you are going to be able to code exactly what they want. State machine diagrams are a very precise way to describe how you want your system to behave. If you can get your requirements in the form of a state diagram, it will become very easy to write correct, testable, and scalable code.

Thank you for reading

Get in touch:

The Startup

Medium's largest active publication, followed by +754K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store