The Five Step Algorithm Turning Nouns Verbs And St
Have you ever sat down to code, opened your editor, and just… stared at the blank screen? You know exactly what the final program should do. You’ve solved similar problems before. But the first line of code feels impossible. Your cursor blinks at you, and your mind goes blank.
If that sounds familiar, here’s some good news: you’re not broken. This isn’t a lack of skill or intelligence. It’s a missing strategy. Most of us were never taught how to start — we were just told to “start coding.” And for anything non-trivial, that advice fails spectacularly.
This article is the second step in a series that will teach you a systematic way to break that paralysis. In Part 1, you learned about stepwise refinement — starting with a single sentence and breaking it down into smaller pieces. That’s the foundation. Now, we’re going to build on it with a repeatable, five-step algorithm that turns any problem description into a working program skeleton.
Think of this algorithm as a cheat code for your brain. No math, no proofs, just a checklist. It comes from real software-engineering research — specifically, a 1983 paper by Russell Abbott called “Program Design by Informal English Descriptions” and a 1971 paper by Niklaus Wirth called “Program Development by Stepwise Refinement.” (Elon Musk’s famous 5-step process for cutting bureaucracy — delete, simplify, optimize, automate — is a cousin of the same idea, applied to organizations instead of code.)
Our running example for this article: a rolling N-day average function. It’s a concrete, intermediate-difficulty problem. You’ve probably seen it in finance, sales forecasting, or any time series analysis. Here’s the problem statement in plain English:
Compute the rolling N-day average of stock prices. The program should allow you to add new prices one at a time and get the current average of the last N prices.
That’s it. That’s the whole problem. Now let’s turn it into code.
Step 1: Nouns — What Are the Things?
Before you write a single line of code, list every noun in the problem description. These are your data objects or stateful objects. Do not default to ‘class’ yet. Start with the simplest container: a variable or a list.
Let’s check that against the data. Our problem statement: “Compute the rolling N-day average of stock prices. The program should allow you to add new prices one at a time and get the current average of the last N prices.”
Here are the nouns:
- stock price (or just “price”)
- day (or “N-day”)
- window (the N in N-day)
- average
In plain English: A noun in the problem is a candidate for a variable, a list, or a class. But start with the simplest container. For now, we’ll just list them in a comment block and assign simple Python containers.
This is the hardest part — resisting the urge to jump to classes. Most beginners over-engineer here. They see “stock price” and think “I need a StockPrice class!” No. Start with a list. You can always refactor later.
Let’s see what happens:
# Step 1: Nouns
# - stock price: a list of floats (prices)
# - day: not directly needed, but the window size is an integer
# - window: an integer (N)
# - average: a float (computed)
window_size = 7 # N in N-day average
prices = [] # list to hold prices
So we have four things. That’s our starting point. We haven’t written any logic yet — just named the data containers.
Step 2: Verbs — What Do the Things Do?
Now list every verb in the problem. These are your functions or methods. Each verb acts on one or more nouns.
Let’s check that against the data. Our problem statement again: “Compute the rolling N-day average of stock prices. The program should allow you to add new prices one at a time and get the current average of the last N prices.”
Here are the verbs:
- compute (the average)
- add (a new price)
- get (the current average)
In plain English: A verb in the problem is a candidate for a function. If it acts on a single noun, it might be a method — but again, start simple.
This is the hardest part — deciding whether a verb is a function or a method. We’ll decide in Step 3. For now, just write function stubs with docstrings that say “placeholder.”
Let’s see what happens:
# Step 2: Verbs
# - compute: compute_average(prices, window_size) -> float
# - add: add_price(price) -> None
# - get: get_average() -> float
def compute_average(prices, window_size):
"""Placeholder: compute the average of the last window_size prices."""
pass
def add_price(price):
"""Placeholder: add a new price to the list."""
pass
def get_average():
"""Placeholder: return the current rolling average."""
pass
So we have three actions. That’s our function skeleton. Notice how we haven’t decided yet whether these are standalone functions or methods of a class. That’s intentional.
Step 3: State — Does It Persist Between Calls?
Here’s where we decide: is this a function or a class? The rule is simple: if data needs to survive from one call to the next, it’s stateful — use a class. If not, use a function.
Let’s check that against the data. For the rolling average, the list of prices must persist between add_price calls. If you call add_price(10) and then add_price(20), the program needs to remember that the first price was 10. That’s state.
In plain English: ‘State’ = data that lives longer than a single function call. If you have state, you need a class (or a closure, but we’ll keep it simple).
This is the hardest part — knowing when state is needed. A good rule: if you find yourself using a global variable, you probably need a class.
Let’s see what happens:
# Step 3: State
# The list of prices must persist between calls -> use a class
class RollingAverage:
"""Compute the rolling N-day average of stock prices."""
def __init__(self, window_size):
self.prices = []
self.window_size = window_size
def add_price(self, price):
"""Add a new price to the list."""
pass
def get_average(self):
"""Return the current rolling average."""
pass
def reset(self):
"""Clear all stored prices."""
pass
The class is just a container for the state (the list of prices) and the verbs that act on it. Notice we added a reset verb — that came from thinking about what else might be needed. The problem statement didn’t mention it, but it’s a natural addition.
Step 4: Interface — What Goes In, What Comes Out, What Can Go Wrong?
For each function, write down the inputs, outputs, and error conditions. This is your contract with the rest of the program.
Let’s check that against the data. For add_price(price), the input is a number. What if it’s negative? What if it’s not a number? For get_average(), what if no prices have been added yet? What if fewer than N prices have been added?
In plain English: ‘Interface’ = the ‘what’ not the ‘how’. It’s the signature and the edge cases.
This is the hardest part — thinking about what can go wrong. Most bugs come from unhandled edge cases.
Let’s see what happens:
# Step 4: Interface
# add_price(price):
# Input: price (float)
# Output: None
# Raises: ValueError if price < 0 or not a number
#
# get_average():
# Input: None
# Output: float (the average)
# Raises: ValueError if no prices have been added
#
# reset():
# Input: None
# Output: None
# Raises: Nothing
class RollingAverage:
"""Compute the rolling N-day average of stock prices."""
def __init__(self, window_size):
self.prices = []
self.window_size = window_size
def add_price(self, price):
"""Add a new price to the list.
Input: price (float)
Output: None
Raises: ValueError if price < 0 or not a number
"""
pass
def get_average(self):
"""Return the current rolling average.
Input: None
Output: float (the average)
Raises: ValueError if no prices have been added
"""
pass
def reset(self):
"""Clear all stored prices.
Input: None
Output: None
"""
pass
Now we know exactly what each piece does. No ambiguity. The interface is the contract — it tells other programmers (and your future self) how to use this class without knowing how it works internally.
Step 5: Docstring First — Then Implement
Write the docstring before the code. This forces you to clarify intent. Then implement the simplest thing that works.
Let’s check that against the data. For get_average, can you explain what it does in one paragraph? If you can’t, you don’t understand it well enough to code it.
In plain English: ‘Docstring first’ = write the contract in plain English, then translate to code.
This is the hardest part — sticking to the docstring. Resist the urge to add extra features. The docstring says “Returns the average of the last N prices. If fewer than N prices have been added, returns the average of all prices so far.” That’s it. No caching. No fancy statistics. Just the simplest thing that works.
Let’s see what happens:
class RollingAverage:
"""Compute the rolling N-day average of stock prices."""
def __init__(self, window_size):
"""Initialize with the window size.
Input: window_size (int) - number of days in the rolling window
Output: None
Raises: ValueError if window_size < 1
"""
if window_size < 1:
raise ValueError("window_size must be at least 1")
self.prices = []
self.window_size = window_size
def add_price(self, price):
"""Add a new price to the list.
Input: price (float) - the new stock price
Output: None
Raises: ValueError if price < 0 or not a number
"""
if not isinstance(price, (int, float)):
raise ValueError("price must be a number")
if price < 0:
raise ValueError("price cannot be negative")
self.prices.append(price)
def get_average(self):
"""Return the current rolling average.
Returns the average of the last N prices.
If fewer than N prices have been added, returns the average of all prices so far.
Input: None
Output: float (the average)
Raises: ValueError if no prices have been added
"""
if not self.prices:
raise ValueError("No prices have been added yet")
recent_prices = self.prices[-self.window_size:]
return sum(recent_prices) / len(recent_prices)
def reset(self):
"""Clear all stored prices.
Input: None
Output: None
"""
self.prices = []
The docstring guided the implementation. No guesswork. The get_average method uses self.prices[-self.window_size:] to get the last N prices, and divides by the actual number of prices in that slice (which could be less than N if we just started).
Let’s test it:
# Test the RollingAverage class
avg = RollingAverage(3)
avg.add_price(10)
avg.add_price(20)
avg.add_price(30)
print(f"Average of [10, 20, 30]: {avg.get_average()} # Expected: 20.0")
avg.add_price(40)
print(f"Average of [20, 30, 40]: {avg.get_average()} # Expected: 30.0")
avg.reset()
avg.add_price(100)
print(f"After reset, average of [100]: {avg.get_average()} # Expected: 100.0")
When you run this, you’ll see:
Average of [10, 20, 30]: 20.0 # Expected: 20.0
Average of [20, 30, 40]: 30.0 # Expected: 30.0
After reset, average of [100]: 100.0 # Expected: 100.0
What this actually means: the first test adds three prices (10, 20, 30) and gets an average of 20.0 — correct. The second test adds a fourth price (40), so the window slides to include only the last three prices (20, 30, 40), giving an average of 30.0 — correct. The third test resets and adds a single price (100), getting an average of 100.0 — correct.
Putting It All Together: The Full Worked Example
Let’s run through the entire 5-step algorithm from start to finish, showing the progression from nouns to working code.
Step 1: Nouns — stock price, day, window, average → prices = [], window_size = 7
Step 2: Verbs — compute, add, get → compute_average, add_price, get_average
Step 3: State — prices persist between calls → class RollingAverage
Step 4: Interface — inputs, outputs, edge cases → detailed comments
Step 5: Docstring first — write contract, then implement → working code
Here’s the final, clean code:
class RollingAverage:
"""Compute the rolling N-day average of stock prices."""
def __init__(self, window_size):
if window_size < 1:
raise ValueError("window_size must be at least 1")
self.prices = []
self.window_size = window_size
def add_price(self, price):
if not isinstance(price, (int, float)):
raise ValueError("price must be a number")
if price < 0:
raise ValueError("price cannot be negative")
self.prices.append(price)
def get_average(self):
if not self.prices:
raise ValueError("No prices have been added yet")
recent_prices = self.prices[-self.window_size:]
return sum(recent_prices) / len(recent_prices)
def reset(self):
self.prices = []
# Test harness
if __name__ == "__main__":
avg = RollingAverage(3)
avg.add_price(10)
avg.add_price(20)
avg.add_price(30)
print(f"Average of [10, 20, 30]: {avg.get_average()}")
avg.add_price(40)
print(f"Average of [20, 30, 40]: {avg.get_average()}")
avg.reset()
avg.add_price(100)
print(f"After reset, average of [100]: {avg.get_average()}")
This same algorithm works for any problem — from a simple function to a complex system. The key is to follow the steps in order and not skip ahead.
You Now Have a Repeatable Process
Let’s recap the five steps as a quick reference:
- Nouns — List every noun in the problem. These are your data containers.
- Verbs — List every verb in the problem. These are your functions or methods.
- State — Does data persist between calls? If yes, use a class. If no, use a function.
- Interface — For each function, write down inputs, outputs, and error conditions.
- Docstring first — Write the contract in plain English, then implement the simplest thing that works.
Try it on a problem you’re currently stuck on. See how it feels. The blank-page freeze doesn’t have to win — you now have a cheat code for your brain.
In the next article, we’ll apply this algorithm to a more complex problem: a multi-file program that builds a mini data pipeline. You’ll see how the same five steps scale from a single class to an entire project.
Check Your Understanding
Remember: List the five steps of the algorithm in order.
Understand: Explain why state (Step 3) is the key factor in deciding between a function and a class. Use the rolling average example to illustrate.
Apply: Given the problem “Write a program that tracks the high score in a video game,” list the nouns, verbs, and state. Then write the class skeleton.
Analyze: Compare the 5-step algorithm to stepwise refinement (from Part 1). How are they similar? How do they differ? Which one would you use for a brand-new problem?
Evaluate: A colleague says “I never use classes — functions are simpler.” Using the rolling average example, explain why a class is necessary here and what would go wrong with a function-only approach.
Create: Choose a problem from your own work or studies. Apply the 5-step algorithm to it. Write the complete class or function skeleton with docstrings. (Don’t implement the logic — just the skeleton.)
Apply What You Learned is for Supporter and Insider subscribers.
Subscribe to unlock the exercises on this post.
See plansRelated articles
- Time Series Under review
Time-Series Cross-Validation: Why K-Fold Breaks on Temporal Data
Learn why K-Fold causes temporal leakage on time-series data and how walk-forward validation delivers honest, production-ready R² metrics you can trust.
- Time Series Under review
Why Your Forecast Fails on Christmas: Handling Multiple Seasonalities and Holiday Spikes
Learn to model overlapping seasonalities and holiday halo effects in Prophet with Fourier series and prior-scale tuning so forecasts survive the Christmas rush.
- Time Series Under review
Reference: Time Series Anatomy
A reference for time series anatomy: trend, seasonality, residual, autocorrelation, and stationarity, with Python decomposition examples and decision rules.
- Time Series Under review
Prophet vs. Statistical Models: A Practical Forecasting Showdown
Compare Prophet and ARIMA head-to-head on messy real-world time series with structural breaks, holidays, and multiple seasonalities to choose the right model.
Looking for something else?
Search every article by title, summary or topic.