Back to Articles

Learning Python from Peter Norvig's Problem-Solving Playbook

[ View on GitHub ]

Learning Python from Peter Norvig's Problem-Solving Playbook

Hook

When the co-author of the world's most influential AI textbook writes Python code for fun, it looks nothing like interview prep—and that's exactly why 24,000 developers have starred his repository.

Context

Most programming practice material falls into two camps: toy tutorials that never touch real complexity, or competition-focused platforms that optimize for speed over clarity. Peter Norvig's pytudes repository occupies a rare middle ground—challenging problems solved with production-quality thinking, but written primarily for learning rather than winning.

The name 'pytudes' is a deliberate nod to musical études: exercises designed to perfect particular skills through deliberate practice. Just as a pianist might work through Chopin's études to master specific technical challenges, programmers can work through Norvig's notebooks to internalize Python patterns, algorithmic thinking, and problem decomposition strategies. The repository has grown organically since 2017, accumulating solutions to Advent of Code puzzles, explorations of probability paradoxes, word games, and recently, investigations into large language model capabilities. Each notebook is a window into how an expert breaks down complex problems.

Technical Insight

What makes pytudes valuable isn't just that the problems are solved—it's how they're solved. Norvig consistently demonstrates patterns that separate experienced Python developers from intermediate ones. Take his approach to the 'Countdown' numbers game, where you combine numbers with arithmetic operations to reach a target. Rather than jumping straight to recursion, he builds up the solution incrementally, starting with data structures:

from typing import Iterator
from itertools import permutations, combinations

Op = tuple[str, callable]  # e.g. ('+', lambda a, b: a + b)
ops: list[Op] = [
    ('+', lambda a, b: a + b),
    ('-', lambda a, b: a - b),
    ('*', lambda a, b: a * b),
    ('/', lambda a, b: a / b if b != 0 else None)
]

def expressions(numbers: list[int]) -> Iterator[tuple[float, str]]:
    """Generate all valid arithmetic expressions from numbers."""
    if len(numbers) == 1:
        yield (numbers[0], str(numbers[0]))
    else:
        for i, j in combinations(range(len(numbers)), 2):
            for op_name, op_func in ops:
                result = op_func(numbers[i], numbers[j])
                if result is not None:
                    remaining = [numbers[k] for k in range(len(numbers)) 
                               if k not in (i, j)] + [result]
                    for val, expr in expressions(remaining):
                        yield (val, f"({numbers[i]} {op_name} {numbers[j]}) = {expr}")

This code demonstrates several patterns worth studying. First, the use of type hints isn't perfunctory—Iterator[tuple[float, str]] documents that we're generating both results and their string representations, a decision that makes debugging and explanation natural. Second, the lambda-in-tuple pattern for operations is a classic functional approach that makes the code extensible. Third, the recursive structure with explicit base case and careful state management (removing used numbers, adding results) shows clean thinking about problem decomposition.

Norvig's probability notebooks showcase another hallmark: using Python's expressiveness to make code read like mathematical notation. In his Bayesian inference problems, you'll find code like:

def P(event, given=None):
    """Probability of event, optionally given conditioning event."""
    if given is None:
        return event.prob()
    else:
        return P(event & given) / P(given)

def normalize(dist: dict) -> dict:
    """Normalize a probability distribution so values sum to 1."""
    total = sum(dist.values())
    return {k: v / total for k, v in dist.items()}

This isn't just solving probability problems—it's creating a domain-specific language within Python. The P(event | given) syntax maps directly to how probability is written mathematically, reducing cognitive load when working through complex problems.

The Advent of Code solutions reveal optimization patterns that matter in real applications. Norvig frequently uses memoization, but not just with @lru_cache. He'll show custom caching strategies when the problem structure demands it, demonstrating when to reach for different tools. His grid-based puzzle solutions consistently use complex numbers to represent 2D coordinates—a beautiful Python trick where 1+2j represents position (1, 2) and rotation becomes multiplication by 1j.

Perhaps most valuable are his explorations of when NOT to optimize. In word game solvers, he'll start with the obvious O(n²) solution, measure it, and only optimize if needed. Comments like "This runs in 0.3 seconds, which is fine" appear frequently, teaching that clarity often trumps premature optimization. When he does optimize, the progression from clear-but-slow to fast-and-still-clear is documented, showing the thought process rather than just the final answer.

Gotcha

The biggest challenge with pytudes is that it's aggressively non-linear. There's no Chapter 1, no prerequisites section, no recommended order. You need to bring your own motivation and context. If you don't know what Advent of Code is, you'll spend time figuring out the problem before you can appreciate the solution. Some notebooks reference probability theory, graph algorithms, or linguistic concepts without introduction—they're written by an expert for people who want to become experts, not for absolute beginners.

The Jupyter notebook format, while great for exploration, means you can't easily run tests, refactor across notebooks, or build on previous solutions. Each notebook is intentionally isolated, which aids understanding but means you'll see similar utility functions reimplemented across notebooks. There's no accompanying video lectures, exercise solutions, or community discussion built into the repository. You're reading someone's personal programming journal, which is both the strength (authentic expert thinking) and the limitation (not pedagogically optimized). If you learn best from structured courses with progressive difficulty and immediate feedback, this free-form collection will feel frustrating. It rewards self-directed curiosity but punishes passive consumption.

Verdict

Use pytudes if you're an intermediate Python developer who learns by reading excellent code, you enjoy puzzles and algorithmic challenges, or you want to see how expert-level problem decomposition actually works beyond interview prep patterns. It's perfect for developers transitioning from 'gets the job done' to 'writes code other experts respect'—the repository is a masterclass in idiomatic Python and clear thinking. Skip it if you need hand-holding through programming fundamentals, prefer structured courses with quizzes and progression tracking, or want production-ready libraries rather than educational explorations. This isn't a course or a framework—it's a glimpse into how one of computing's most respected minds approaches problems, which is invaluable if you're ready to learn by osmosis but insufficient if you need scaffolding.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/ai-dev-tools/norvig-pytudes.svg)](https://starlog.is/api/badge-click/ai-dev-tools/norvig-pytudes)