Let’s write an essay! But that’s an impossible task. However, we can create a process that, if run forever, would generate an infinitely long essay. . infinitely long Close enough Now, you can obviously generate a long and repetitive essay with a single line of Python code: >>> "This is water. " * 20 'This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. ' … boooring! Instead, we will generate a much more interesting essay using the State design pattern in this article. Yaawn First, we will understand what state machines are and how they’re related to the State design pattern. Next, we will create a state machine that can generate a (reasonably) interesting and infinite essay. Then, we will briefly go over the state design pattern. Finally, we will translate that state machine into object-oriented code using the state design pattern. Software design patterns are effective ways to solve commonly occurring problems. When applied appropriately, software design patterns like the state pattern can help you write better scalable, maintainable, and testable software. State Machine In essence, the State design pattern translates a State Machine into object-oriented code. If you aren’t familiar with state machines, it’s a pretty simple concept. A state machine has and . States are certain properties of our system of interest, and state transitions are actions that change these properties and thereby also cause a state change. states transitions Since I have a Robotics background (among other things) and because state machines are used extensively in Robotics, I’ll use a simple example of a robot vacuum cleaner to illustrate how state machines work. The state machine diagram paints an intuitive picture of how the robot operates, even if you’ve never encountered state machines. Let’s go over this operation step by step. The robot starts in the state (the black dot indicates the starting state). Docked If the robot detects its battery is low, it begins charging itself ( state) until its battery is full. Once the battery is full, it returns to the state. Charging Docked In the state, if the robot detects that the floor is dirty (and its battery is not low), it starts cleaning the floor ( state). Docked Cleaning In the state, if the robot gets low on battery, then it goes to charge itself. And if the floor is clean, the robot returns to its dock ( state). Cleaning Docked Thus, our robot vacuum cleaner has three states — , , and — and has transitions based on sensory detection of the floor and its battery. Docked Cleaning Charging Simple state machine for infinite essay Now that we understand state machines at a basic level let’s create a state machine capable of writing an infinite essay. Above is a state machine that uses English grammar to generate an essay comprised of short, simple sentences. I promise we’ll get to a more interesting version very soon, but this should serve as a good starting point for understanding. Let’s go over how it works. Starting in the state, we generate a noun by picking from some pre-defined list of nouns. Let’s say our noun is “The World”. (sentence so far: “The World”) Noun Then we end up in the state, generating a verb next (say, “barks”). (sentence so far: “The World barks”) Verb We generate an adjective (say, “red”) in the state. (sentence so far: “The World barks red”) Adjective Then, in the state, we generate one of the terminating punctuation marks, say “!”. (sentence: “The World barks red!”) Endmark Finally, we’re back in the state to generate our next sentence in the essay. Noun This state machine might generate a (nonsensical) essay that looks like this. The World barks red! Cousin Harry rains foul? The tigers shimmer fun. … Non-deterministic state machine for infinite essay Although “non-deterministic” sounds complicated, for our purposes, it just means adding in some randomness. Essentially we’re adding a kind of a coin toss before transitioning to some of the states. You’ll see what I mean. The non-deterministic state machine above is very similar to the one before. The only differences are: Negations are words like “no” or “not,” and conjunctions are words like “and” and “but.” In the state, we generate a verb and then we toss a coin. If it lands heads (50% probability), we go to the state; otherwise we go to the state. Verb Negation Adjective Similarly, in the state, we generate an adjective and then toss a coin. If heads, we go to the state; if it is tails, then we go to the state. Adjective Conjunction Endmark With the introduction of randomness, negation, and conjunctions, we can now generate more interesting and variable-length sentences. State Design Pattern Now, let’s understand how the state design pattern works. Again, remember that we’re trying to translate a state machine to object-oriented code. In the essay generation state machine, observe that every state needs to do two things. Perform some action. In this case, generating a word (noun, adjective, etc.). Transition to the next state. From to , and so on. Noun Verb From the point of view of a particular state, there is that it needs to know about or do. Instead of being bogged down by the complexity of the entire system — all its states and transitions — we can just focus on one state at a time. In my view, this kind of and is the biggest selling point of the State pattern. nothing else isolation decoupling Below, we have a diagram for the State design pattern. There is some context in which each of the states operates, illustrated by the class. The context object has a private state attribute, which it uses to call the current state to perform its action. Each state implements a interface with methods for performing its action or operation and returning the next state. UML Context State If we map this onto the essay generation example, the UML diagram looks like this. is now an abstract class (indicated by italics) instead of an interface. Abstract classes can have some abstract (not implemented) methods and attributes, while others can be defined. Interfaces are totally abstract: all their methods are abstract. I made this change because implementation is the same across all the states, and avoiding duplicate code is good. WordState generateWord Let’s break down each of the attributes and methods above. In the class, we have: EssayContext : Reference to the current object. state WordState : List of all the words generated so far. essayBody : Setter to change the attribute. setState() state : Method to add the next word to the essay body. addWord() : We call this method to generate our essay; we stop when the has length greater than . generateEssay() essayBody length : Returns a string of the generated essay. printEssay() In the abstract class , we have: WordState : Abstract property (indicated by italics) for a list of words from which we choose words to generate. wordList : Method which adds generated word to the essay context. generateWord() : Abstract method for returning the next state. nextState() We’ll use as a representative example for all the other concrete states inherited from . NounState WordState : A list of nouns from which we choose words to generate. wordList : Returns the next state. nextState() Now, we have everything we need to actually implement this in code. Let’s go ahead and do just that! Python Code Let’s first write the class in a file called . We'll ditch camel case and switch to snake case because, well, Python is a... snake (sorry). EssayContext essay_context.py from word_state import WordState class EssayContext: def __init__(self, state: WordState): self.state = state self.essay_body: list[str] = [] def set_state(self, state: WordState): self.state = state def add_word(self, word: str): self.essay_body.append(word) def generate_essay(self, length: int): while len(self.essay_body) < length: self.state.generate_word(self) def print_essay(self) -> str: return " ".join(self.essay_body) Then, let’s add the states in a file called . word_state.py import abc import numpy as np class WordState(abc.ABC): word_list: list[str] @classmethod def generate_word(cls, context: "EssayContext"): word = np.random.choice(cls.word_list) context.add_word(word) context.set_state(cls.next_state()) @classmethod @abc.abstractmethod def next_state(cls) -> "WordState": pass class NounState(WordState): word_list: list[str] = ["everything", "nothing"] @classmethod def next_state(cls): return VerbState class VerbState(WordState): word_list: list[str] = ["is", "was", "will be"] @classmethod def next_state(cls): heads = np.random.rand() < 0.5 if heads: return NegationState return AdjectiveState class NegationState(WordState): word_list: list[str] = ["not"] @classmethod def next_state(cls): return AdjectiveState class AdjectiveState(WordState): word_list: list[str] = ["fantastic", "terrible"] @classmethod def next_state(cls): heads = np.random.rand() < 0.5 if heads: return ConjunctionState return EndmarkState class ConjunctionState(WordState): word_list: list[str] = ["and", "but"] @classmethod def next_state(cls): return NounState class EndmarkState(WordState): word_list: list[str] = [".", "!"] @classmethod def next_state(cls): return NounState Finally, let’s add code to run everything in . main.py from essay_context import EssayContext from word_state import NounState if __name__ == '__main__': ctx = EssayContext(NounState) ctx.generate_essay(100) print(ctx.print_essay()) Running gives us the following output (different each time because of non-determinism): python main.py 'everything is not terrible and nothing was terrible ! everything will be not fantastic but everything is fantastic . everything will be fantastic . nothing will be fantastic and nothing will be terrible ! everything was not fantastic and everything will be not terrible . everything was terrible . nothing was terrible but nothing will be fantastic ! nothing is not terrible . nothing was not fantastic but everything was not fantastic ! everything will be not fantastic but everything will be terrible ! everything will be not fantastic . everything is fantastic but nothing will be not terrible ! everything will be not fantastic but nothing was not fantastic !' Not bad for such a simple system! We can also extend the various word lists or add more states to make the essay generation more sophisticated. We could even introduce some LLM APIs to take our essays to the next level. Final thoughts State machines and the State pattern are a great fit to model and create systems with a well-defined notion of a “state.” That is, there are specific behaviors and properties associated with each state. The robot vacuum cleaner is cleaning, docked or charging. Your TV can be ON or OFF, and the TV remote buttons will act differently based on the TV’s state. It’s also a good fit for generating or identifying sequences with a well-defined pattern. This applies to our essay generation example. Finally, you might ask, “What’s the point of all this?” Why did we go through all the trouble defining the various states and classes to generate this “infinite” essay? We could have written 20 (or fewer) lines of Python code to achieve the same behavior. The short answer is for better . scalability Imagine if, instead of just three or five states, we had 50 or 500 states. This isn’t hyperbole; real enterprise systems do have that level of complexity. Suddenly, the State pattern seems much more appealing because of its decoupling and isolation. We can just focus on one state at a time without having to keep the entire system in our heads. It’s easier to introduce changes since one state won’t affect others. It also allows for easier unit testing, a big part of a scalable and maintainable system. Ultimately, the State pattern is not just about managing states; like all design patterns, it’s a blueprint for building systems that are as scalable and maintainable as they are sophisticated.