Introduction
This blog will help you to erase almost all of your doubts about state management in OOPS and help you write more scalable code.
I will explain everything using a WPF app with UI. You can download the attached code for reference.
I am not following the MVVM design pattern in this project; rather, I'm using code behind because our main focus here is to understand state design pattern.
Note
I am explaining this with a WPF application, so there is a lot of code that I can't put in here, I request you to download the attached source code. (Following are the all screens (state-wise).)
New state

After successful submit.
If user presses cancel.
If the date expires.
When an application is in closed state.
If ticket is booked successfully.
If the user cancels after pending state.

First of all, why are we even doing this? What is wrong with a naive approach?
- The greater the number of states, the more interdependent their logic becomes
- More time to manage state across the application
- Code is no longer extensible as it has way too many dependencies and it needs a lot of refactoring every time a new state is added or altered
- Harder to debug each state which is tangled with other states. I mean who are we kidding, I am a developer and I know how frustrating debugging can be.
Here state design pattern comes to the rescue: It minimizes the complexity and tackles all of the problems faced above
So what does state design pattern do?
- It encapsulates state-specific behaviour within a separate state object
- A class delegates the execution of its state-specific behaviour to one state at a time instead of implementing state-specific behaviour itself.
Have a good look at the following conceptual diagram. Don't worry for now what this means, once we code it will be cleared up.

Explanation of the diagram,
- The context is a class which maintains an instance of a concrete state as its current state.
- The abstract state is an abstract class that defines an interface encapsulating all state-specific behaviours.
- A concrete state is a subclass of the abstract state that implements behaviours specific to a particular state of the context.
Looked at another way, we have the context, an abstract state, and any number of concrete states.
The concrete states derive from the abstract state implementing the interfaces defined in it.
The context maintains a reference to one of the concrete states as its current state via the abstract state base class.
State design pattern was developed to overcome 2 main challenges,
- How can an object change its behaviour when its internal state changes.
- How can state-specific behaviours be defined so that states can be added without altering the behaviour of existing states?
I will code both the naive approach and then a state transition pattern approach.
Let's take a real-life example.
Assume you're visiting IRCTC's website. while booking a ticket your object might fall under one of the following states.
Now here is the problem,
When the state is new, the user can go into submit state or also a user might cancel a booking and go into close-state or a date might expire and the object can go into close-state.
Now, these are a few scenarios.
How can I tackle this problem with a naive approach? By creating a new boolean variable for each state and changing dependent code.
That just creates too much interdependency.
Assume we have a class Booking, which after following a naive approach might look like this.
Note
The naive approach may look easy because it's ready-made code, but it is very difficult to manage, knowing all the interdependencies.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using State_Design_Pattern.UI;
- namespace State_Design_Pattern.Logic
- {
- public class Booking
- {
- private MainWindow View { get; set; }
- public string Attendee { get; set; }
- public int TicketCount { get; set; }
- public int BookingID { get; set; }
- private CancellationTokenSource cancelToken;
- /// <summary>
- /// boolean to maintain new state
- /// </summary>
- bool isNew;
- /// <summary>
- /// boolean to maintain pending state
- /// </summary>
- bool isPending;
- /// <summary>
- /// boolean to maintain booked state
- /// </summary>
- bool isBooked;
- public Booking(MainWindow view)
- {
- View = view;
- isNew = true;
- BookingID = new Random().Next();
- ShowState("New");
- view.ShowEntryPage();
- }
- public void SubmitDetails(string attendee, int ticketCount)
- {
- if (isNew)
- {
- isNew = false;
- isPending = true;
- Attendee = attendee;
- TicketCount = ticketCount;
- //to cancel submited booking.
- cancelToken = new CancellationTokenSource();
- StaticFunctions.ProcessBooking(this, ProcessingComplete, cancelToken);
- ShowState("Pending");
- View.ShowStatusPage("Processing booking");
- }
- }
- public void Cancel()
- {
- if (isNew)
- {
- ShowState("Booking cancelled");
- View.ShowStatusPage("Cancelled by user");
- isNew = false;
- }
- else if (isPending)
- {
- cancelToken.Cancel();
- }
- else if (isBooked == true)
- {
- ShowState("Closed");
- View.ShowStatusPage("Booking canceled: Expect a refund");
- isBooked = false;
- }
- else
- {
- View.ShowStatusPage("Booking cannot be cancelled");
- }
- }
- public void DatePassed()
- {
- if (isNew)
- {
- ShowState("Booking cancelled");
- View.ShowStatusPage("Booking expired!");
- isNew = false;
- }
- else if (isBooked == true)
- {
- ShowState("Closed");
- View.ShowStatusPage("We hope you enjoyed the event");
- isBooked = false;
- }
- }
- public void ProcessingComplete(Booking booking, ProcessingResult result)
- {
- isPending = false;
- switch (result)
- {
- case ProcessingResult.Sucess:
- ShowState("Booked");
- View.ShowStatusPage("Enjoy the Event");
- break;
- case ProcessingResult.Fail:
- isNew = true;
- View.ShowProcessingError();
- Attendee = string.Empty;
- BookingID = new Random().Next();
- ShowState("New");
- View.ShowEntryPage();
- break;
- case ProcessingResult.Cancel:
- ShowState("Closed");
- View.ShowStatusPage("Processing Canceled");
- break;
- }
- }
- public void ShowState(string stateName)
- {
- View.grdDetails.Visibility = System.Windows.Visibility.Visible;
- View.lblCurrentState.Content = stateName;
- View.lblTicketCount.Content = TicketCount;
- View.lblAttendee.Content = Attendee;
- View.lblBookingID.Content = BookingID;
- }
- }
- }
Problem with the above code,
First problem: When the booking was in a new-state: cancel method updates the UI saying it's cancelled, when the booking was in pending-state: cancel method updates the UI saying it's pending.
So each time a new state is added: A new boolean field is added to track the different state. New code has to be written to maintain the balance - which increases the complexity whenever a new-state is added.
I had to make and manage 3 booleans and tangle them with each other which makes code complex. Also note this is just one class; it took more than enough in other respected classes.
Rather than doing this, we are going to do the following.

BookingContext is a context for the state pattern,
BookingState is an abstract class for the state pattern.





Join the conversation! Your thoughts help the community grow.