Building Something From All of It
Every chapter until now has proven one idea in isolation. Build one program that needs variables, a loop, a function, and a dictionary all in the same twenty lines, and watch them stop being separate topics.
Every chapter until now has proven one idea in isolation. Build one program that needs variables, a loop, a function, and a dictionary all in the same twenty lines, and watch them stop being separate topics.
Planning the shape before writing a line
The project: a command-line word-frequency counter. Give it a sentence, and it reports how many times each word appears — the smallest program that needs a loop, a dictionary, and a function working together rather than in isolation.
It is a deliberately small choice. A word counter is not an impressive-sounding project, and that is the point — every piece of it maps onto something a previous chapter already taught, so nothing about finishing it depends on learning something new mid-build. What it asks of you is putting those pieces together correctly, which turns out to be its own separate skill from knowing each one in isolation.
Minimum version
Stretch version
- 1
Split the text into words
A string method turns one long sentence into a list of individual words.
- 2
Count each word with a dictionary
Loop over the words, using each one as a key and a running count as its value.
- 3
Wrap it in a function
word_counts(text) takes a sentence in and returns the finished dictionary out.
- 4
Test it before trusting it
One normal sentence, and one edge case — an empty string — each with an assertion.
Building it piece by piece, testing each one
$ >>> text = "the cat sat on the mat the cat ran"$ >>> words = text.split()$ >>> words$ ['the', 'cat', 'sat', 'on', 'the', 'mat', 'the', 'cat', 'ran']split() with no argument breaks on any run of whitespace — the comprehensions and loops chapters both used exactly this kind of list as their starting point.
def word_counts(text): counts = {} for word in text.split(): counts[word] = counts.get(word, 0) + 1 return countscounts.get(word, 0) returns the running total if word has been seen before, or 0 if this is its first appearance — the same dictionary-with-a-default pattern from the dictionaries chapter, now doing real work.
{'the': 3, 'cat': 2, 'sat': 1, 'on': 1, 'mat': 1, 'ran': 1} — and assert word_counts("") == {} passes too, because an empty string's split() produces an empty list, and the loop simply never runs.
Both of those checks are worth writing as real assert statements before moving on, not just typed into a shell and read by eye — the testing chapter's whole argument was that a check you write down catches a regression a check you only glance at will not.
def test_counts_repeated_words(): result = word_counts("the cat sat on the mat the cat ran") assert result == {"the": 3, "cat": 2, "sat": 1, "on": 1, "mat": 1, "ran": 1} def test_empty_string_returns_empty_dict(): assert word_counts("") == {}Extending it once the basics hold
The minimum version above is finished, honestly — but it has a known gap. Feed it two sentences that repeat a word with different punctuation attached, and it counts them as different words entirely, because "cat." and "cat" are different strings as far as a dictionary key is concerned.
$ >>> word_counts("The cat, calm, sat. The cat ran fast.")$ {'The': 2, 'cat,': 1, 'calm,': 1, 'sat.': 1, 'cat': 1, 'ran': 1, 'fast.': 1}Seven keys for what a person reading the sentence would call six distinct words — 'cat,' and 'cat' land as two separate entries purely because one of them happened to sit next to a comma. Fixing it needs two small additions, both already covered: str.lower() from the strings chapter to fold case, and str.strip(string.punctuation) to remove punctuation from the edges of each word before it becomes a key.
import string def word_counts(text): counts = {} for word in text.lower().split(): word = word.strip(string.punctuation) counts[word] = counts.get(word, 0) + 1 return countsWith that change, word_counts("The cat, calm, sat. The cat ran fast.") returns {'the': 2, 'cat': 2, 'calm': 1, 'sat': 1, 'ran': 1, 'fast': 1} — six keys instead of seven, and 'cat' correctly counted twice. It is a genuine improvement, and also a reminder that "finished" for a real program is a judgement call, not a fixed line: the version without this fix was complete enough to call done a section ago, and this version is more correct without either one being the objectively right answer for every use.
One more extension worth trying alone, using only what this track already covers: the most common word. max(counts, key=counts.get) finds the key whose value is largest without writing a loop yourself — the same key= argument idea the sorting built-ins in Python use throughout the standard library.
Where to go from here
Nothing about this program is specific to counting words — the same shape, a loop filling a dictionary, is the core of a shopping cart total, a vote tally, or a log file summary. The twenty-four chapters behind this one are not separate tools; they are the vocabulary this one program was written in.
They are also not everything. This track never touched a database, never built anything a browser could talk to, and never covered the tooling that turns a script into something you could hand to a stranger and have it just work on their machine. That is not an oversight to feel behind on — it is simply where this particular map ends, and where the next ones start.
Flask or FastAPI turn a function like word_counts into something a browser can call — the same function, behind a new front door.
pandas replaces hand-rolled dictionaries and loops once a dataset stops fitting comfortably in your head, or in memory.
Adding hints, def word_counts(text: str) -> dict:, and running a checker like mypy over them catches a category of bug this track never asked you to think about.
Contributing to an existing open-source project teaches you to read code you did not write, which is a different skill from writing your own from scratch.
Pick whichever one of those solves a problem you actually have, rather than the one that sounds most impressive — the word counter above did not become useful because it was ambitious. It became useful because every idea in it was one you had already proven you understood on its own, put to work together.
Before calling the minimum version finished
- →word_counts(text) returns a dictionary, not a printed string — printing is a separate, later step.
- →An empty string produces an empty dictionary, not an error.
- →Punctuation attached to a word ("cat," versus "cat") is counted separately in this version — the extension above shows one way to close that gap.
- →Every function in the program has at least one assert proving what it claims to do, written down rather than checked once by eye.
- →You could hand the function to someone else and they'd know what it returns without reading the body — the name and a docstring say enough on their own.
Key takeaways
- A real program combines ideas that were taught separately — this one needed a loop, a function, and a dictionary together, not in isolation.
- counts.get(word, 0) is the dictionary-with-a-default pattern from earlier in the track, doing work here for the first time.
- Testing the empty-string case here is the same habit the testing chapter argued for: prove the edge, not just the common case.
- "Finished" is a judgement call, not a fixed line — the minimum version and the extended one are both legitimately complete, for different purposes.
- The shape of this program — loop, accumulate into a dict, wrap in a function — recurs constantly outside of word counting specifically.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.