Most of the processes that people use in their work lives can be represented as some object that goes through some flow, just to name a few examples Software engineers using tasks. The tasks are created first, then they are assigned to an engineer, then planned, then in progress and in the end closed, but can be re-opened later. Delivery order. A customer makes a trackable order, then an item gets dispatched, then delivered to a post office, then collected.  Influencer post. An influencer uploads video to a social platform, updating a draft, open collaboration with someone who makes a set of changes to the video, publication of the video.   Recruitment process.  Can be rejected or continued. Each of the flows above contains many different states, but ont things they have in common - there is always one state where the flow starts and at least one where the flow ends. That’s actually called a finite-state machine. In this article I want to suggest one of many approaches to how to effectively handle flow based systems.  For the sake of concrete example, I will use the recruitment process where candidate applications go through a review flow and I will use C# to express some ideas in code. Please note that it's a very lightweight implementation where I’m not touching communication with databases and don’t go too much into details on the UI part, my main goal is to share the approach. Before going further it’s important to agree on what we understand by a state. Here by state I will mean a description of the object that includes: Position of the object in the flow.  Transitions to move further in the flow. List of allowed mutations within the state.  List of permissions for mutations and views.  Status is the most important attribute of a flow system as it essentially keeps a snapshot of the system. It’s most often stored in a field of an object as enum value, but there are scenarios in which it can be calculated. For now the field approach is used for our candidate application: And we can make a status property for our application: By transition in this article I mean mutations that move an object from one state to another. For example when a candidate application is reviewed by a recruiter it goes to a technical interview stage.  As we already exposed Status to public API we can directly set it there, but in general transition from one status to another goes along with some other side actions (for example through observer code pattern to send notification) and validation (we cannot skip some states), so it might be better to make status change through a function.  Here by mutation I mean a mutation that changes the object, but doesn’t change its  state. Essentially it’s a set of functions that change data within an object. Permissions are a set of rules that dictate what users can do and see. For example, a recruiter cannot leave a note on the candidate application that they’ve successfully passed a technical interview, this should be handled by the interviewer.  Here we need to introduce the concept of the user who will load the applications. I won’t go into much detail, in the scope of this article we only care about the user role. And we assign a role to a new user on creation. This user is a representation of an actual user who owns the session. For example a recruiter who opened an “Interview” tool.  Now that we have a way to understand who is currently working with the data we can load it in a privacy aware manner. In general it means that when creating an instance of a candidate application we pass the user and before fetching data from a database we check if the user should have access to it at this point of time. It also gives us a way to do permission check in all the functions (both mutation and view) of the candidate application. But in a very cumbersome and hard to maintain way. Here we go to abstractions for help!  Some problems with the approach above: Maintainability of mutation permissions.  Just imagine that you need to write that many ifs in every mutation or view function Maintainability of transitions. Similar to permissions they need to check the status and permissions.  Let’s abstract the permission logic to a state abstraction with three main features: Every state will handle permission for viewing, actions and transitions by giving user specific permission tokens that are valid in some state.  States contain all possible transitions to available states.  Every state maps to one and only one status. You might notice that this is actually a structure for a directed graph (or a finite state machine)!  Here is a piece of code that suggests requirements for the states Here I put an example of a CV_SREEENING state implementation Now if by status we can get state, the implementation of permission checks would be just a couple of lines that are also very easy to read.  We can do this by just putting status to state transition in one place.  With that editTechnicalReviewNote method turn into this   And we can use canTransitToStatus to do additional validation check when transitioning between states.  Representing the system as a finite state machine has a huge advantage when it comes to testing as it allows to easily move the system to the needed state and go from there.  One of the ways it can be implemented is by creating an object in the start state and then using state transitions this object can be moved to the target state. For finding a path of transitions we can traverse the state machine with bfs: This makes it extremely easy to quickly transition to a valid state of the candidate application and then use it to test some specific features.  This approach is extremely beneficial when you start working with UI, because all actions and transitions are gated with permission tokens and you don’t have to duplicate logic on frontend and backend to tell when some button should be available or not, you just need to expose permission tokens (say with graphql) and check for need token presence.  There are certain cases, when you want your state machine to have active not one, but two or more active states. In this case one of the approaches is to not store the status as a field, but calculate it based on the change log of the object and do small modification to state code to unify data from different states.  One approach that I find interesting for this case, but to be fair quite hard to support future migrations is using gating for start and finish in the form of Disjoint Normal Form. For example if we add a new state to a candidate application called DESIGN_INTERVIEW that we want to handle simultaneously with TECHNICAL_INTERVIEW we can represent this with history gating like this  Status is DESIGN_INTERVIEW if history includes (CV_SCREENING_FINISH & DESIGN_INTERVIEW_STARTED) records and doesn’t include (DESIGN_INTERVIEW_FINISHED_WITH_SUCCESS) | (DESIGN_INTERVIEW_FINISHED_WITH_FAILURE). Status is TECHNICAL_INTERVIEW if history includes (CV_SCREENING_FINISH & TECHNICAL_INTERVIEW_STARTED) records and doesn’t include (TECHNICAL_INTERVIEW_FINISHED_WITH_SUCCESS) | (TECHNICAL_INTERVIEW _FINISHED_WITH_FAILURE). Status is HIRED if history includes (TECHNICAL_INTERVIEW_FINISHED_WITH_SUCCESS & DESIGN_INTERVIEW_FINISHED_WITH_SUCCESS).  But again, here we sacrifice a lot on maintainability, I just find this method interesting.  In this article I shared an approach to work with flow systems representing them as finite state machine that is pretty easy to maintain, test and leverage on the UI. This approach can be extended even further removing dependency of object from its state definition by abstracting mutation in a separate builder class and checking mutation on commitments but this topic might be  worth of another article!