What you'll learn
- Pick project ideas sized to your level that you will actually finish
- Read other people's code on purpose, as a weekly habit with a method
- Make a first open-source contribution without needing anyone's permission
- Practice deliberately: small, regular, slightly uncomfortable, and reviewed
Concept
Skill after a course comes from projects, and projects fail for one reason more than any other: they are too big. The right size for a project is one you can finish in two to four sessions while learning exactly one new thing. Finishing matters more than ambition, because a finished project is something you can show, extend, and learn from, and an abandoned one is none of those. Keep a list, pick the smallest that still excites you, define done before you start, and stop when you reach it.

Ideas by level, each finishable in a few sessions and each stretching one skill. Fundamentals: a unit converter with tests, a flash-card quiz that tracks scores in a file, a text adventure with a dictionary of rooms. Beginner: a bookmark manager on sqlite with a CLI, a Markdown-to-HTML converter, a recipe scaler that reads JSON, a folder de-duplicator by content hash. Advanced: a typed and tested API client for a public service you use, a static site generator, a log analyser with generators and Counter, an async link checker for a website. Expert: package and publish one of the above, add a Docker image and CI, contribute a fix to a library you depend on, write a small web service with a real database and a deploy.
| Level | Project ideas | The skill it stretches |
|---|---|---|
| Fundamentals | unit converter · flash cards with saved scores · text adventure | functions, files, dictionaries, loops |
| Beginner | bookmark manager (sqlite + CLI) · Markdown converter · duplicate finder | classes, modules, sqlite, pathlib, argparse |
| Advanced | typed API client with tests · static site generator · async link checker | typing, pytest, generators, asyncio, httpx |
| Expert | publish a package · dockerized web service · a fix to a dependency | packaging, CI, Docker, contributing |
Reading code is a practice, not an accident. Once a week, open a well-regarded project on your path and read one module the way the codebase lesson taught: orient, pick one behavior, trace it, draw it. Notice how they name things, how they structure a package, where their tests live, how they handle errors. Steal the habits you like. The standard library itself is excellent reading: pathlib, dataclasses, and functools are written by people who know the language deeply, and they are installed on your machine.
Contributing to open source needs no invitation. Start with the projects you already use: their issue trackers have bugs labeled good first issue, documentation with typos and gaps, and tests that could cover more. Read CONTRIBUTING.md, make the small change in a branch, run their checks, and open a pull request with a clear description. Your first contribution will be a docstring or a test, and that is exactly right; maintainers remember people who make careful small changes and come back. Every contribution is also a code review from someone better than you, which is the fastest feedback there is.
Practice compounds when it is deliberate: regular rather than heroic, so twenty minutes daily beats a monthly weekend; slightly uncomfortable, a step past what you can already do; and reviewed, by a test suite, a linter, a mentor, or a pull request, so mistakes are found. Keep a learning log with one line per session: what you did, what confused you, what you will try next. Read it monthly, and you will see progress that is invisible day to day.

Code
A project picker that sizes ideas to your level and time, and prints a "done" sentence to fill in. Editing the two variables at the top is the whole interface.
# pick_project.py
IDEAS = {
"fundamentals": [("unit converter with tests", "functions, tests"), ("flash cards with saved scores", "files, dicts"), ("text adventure", "dicts, loops")],
"beginner": [("bookmark manager: sqlite + CLI", "sqlite, argparse"), ("Markdown to HTML converter", "strings, regex-lite"), ("duplicate file finder", "pathlib, hashing")],
"advanced": [("typed API client with tests", "typing, httpx, pytest"), ("static site generator", "pathlib, templates"), ("async link checker", "asyncio, httpx")],
"expert": [("publish a package to PyPI", "packaging, CI"), ("dockerized web service", "Docker, config, logging"), ("fix a bug in a dependency", "reading code, PRs")],
}
level = "advanced"
sessions_available = 3
for i, (idea, skills) in enumerate(IDEAS[level], start=1):
fits = "fits" if sessions_available >= 2 else "too little time; shrink it"
print(f"{i}. {idea:<32} stretches: {skills:<24} [{fits}]")
print()
print("Done when: ______________________________ (one sentence, written before you start)")
print(f"Plan: {sessions_available} sessions x ~90 min; commit after each; README before calling it finished")
1. typed API client with tests stretches: typing, httpx, pytest [fits]
2. static site generator stretches: pathlib, templates [fits]
3. async link checker stretches: asyncio, httpx [fits]
Done when: ______________________________ (one sentence, written before you start)
Plan: 3 sessions x ~90 min; commit after each; README before calling it finishedReading practice, applied to the standard library on your own machine: locate a module and measure it, which is the first step of the orientation method on code written by experts.
# read_stdlib.py
import dataclasses
import functools
import inspect
import pathlib
for module in (pathlib, dataclasses, functools):
source = inspect.getsource(module)
docstring = ((module.__doc__ or "").strip().splitlines() or ["(no module docstring)"])[0]
print(f"{module.__name__:<12} {len(source.splitlines()):>5} lines | {docstring[:50]}")
print()
print(inspect.getsource(functools.cache).strip().splitlines()[0])
print(inspect.getdoc(functools.cache).splitlines()[0])
pathlib 1307 lines | Object-oriented filesystem paths.
dataclasses 1813 lines | (no module docstring)
functools 1165 lines | functools.py - Tools for working with functions an
def cache(user_function, /):
Simple lightweight unbounded cache. Sometimes called "memoize".A learning log that costs one line per session and pays off monthly. Appending is the habit; the summary is the reward.
# learning_log.py
from collections import Counter
from datetime import date
from pathlib import Path
log = Path("learning_log.md")
entries = [
("2026-09-01", "typed API client", "confused by Protocol vs ABC", "reread lesson 40"),
("2026-09-02", "typed API client", "mypy strict on Optional", "add None checks"),
("2026-09-04", "typed API client", "nothing; shipped v0.1", "start async version"),
("2026-09-06", "read httpx source", "how transports are swapped", "try MockTransport in my tests"),
]
log.write_text("# Learning log\n\n" + "".join(f"- {d} · {did} · confused: {confused} · next: {nxt}\n" for d, did, confused, nxt in entries), encoding="utf-8")
lines = [ln for ln in log.read_text(encoding="utf-8").splitlines() if ln.startswith("- ")]
projects = Counter(ln.split(" · ")[1] for ln in lines)
print(f"{len(lines)} sessions logged; by project: {dict(projects)}")
print("most recent next step:", lines[-1].split("next: ")[1])
print("today would be entry", len(lines) + 1, "on", date(2026, 9, 9))
4 sessions logged; by project: {'typed API client': 3, 'read httpx source': 1}
most recent next step: try MockTransport in my tests
today would be entry 5 on 2026-09-09Try it yourself
Write your idea list: at least six projects across two levels, each with a one-sentence "done". Pick one and schedule the first session in your calendar. Choose one open-source project you use, find its contributing guide and one "good first issue" or documentation gap, and make a note of exactly what your first pull request will change. Create learning_log.md and write today's line.
Common mistakes
- Projects sized to impress. The ambitious project you abandon teaches less than the small one you finish and extend.
- Reading code only when forced. A weekly deliberate read of a good project is where taste and vocabulary come from.
- Practicing only what you can already do. Comfortable repetition feels productive; the growth is one step past comfort, with feedback.
What's next
- Pick small projects with a written "done", finish them, reflect, repeat; the list by level gives you a year of them.
- Read good code weekly with the orientation method, starting with the standard library; contribute small, careful changes to projects you use.
- Practice that is regular, slightly uncomfortable, and reviewed compounds; a one-line learning log makes the progress visible.
You know how to keep improving. Next, how to turn what you have learned into work: a portfolio, a README people trust, and a first production project for a job or a client.