Python Developer Beginner to Expert
A roadmap for Python from syntax and data structures through the object model, typing, testing, async, web frameworks, packaging, and running Python in production.
Python is easy to start and deceptively deep, which produces a common shape of developer: fluent in syntax, productive in a framework, and unsure what happens when the code meets real traffic. This roadmap is ordered to close that gap, so the later stages are deliberately about running Python rather than writing it.
Go in order. Two stages are worth resisting the urge to skip: the object model, because Python’s protocols are how the whole ecosystem composes and you cannot read framework source without them, and concurrency, because getting threads-versus-processes backwards is the most expensive mistake on this list. Optional topics are branches rather than gaps. Nobody needs Cython to be an expert.
Build something small at each stage. A stage is finished when you have shipped a thing that uses it and been mildly annoyed by an edge case, rather than when you have read about it.
That diagram is the argument for the last two stages. The GIL is an abstract fact until you have to choose a gunicorn worker count. Lockfiles are bureaucracy until an unpinned transitive dependency changes behaviour between your test run and your deploy. And the Lambda-to-Postgres connection problem is the clearest example of something no amount of Python knowledge prepares you for, because the mistake is architectural rather than linguistic.
Language Foundations
Syntax, control flow, and functions
Variables, conditionals, loops, functions, default and keyword arguments, and f-strings. Learn why mutable default arguments are a trap, since the default is evaluated once at definition time.
Built-in data structures
list, tuple, dict, set. Know the cost of each operation: membership testing is O(n) in a list and O(1) in a set, which is the single most useful performance fact for a beginner.
Comprehensions and generators
List, dict and set comprehensions, plus generator expressions and the yield keyword. Generators matter because they let you process data larger than memory.
Mutability and references
Understand that assignment binds a name to an object rather than copying it, and what that means for lists passed into functions. Learn copy vs deepcopy.
Errors and exceptions
try/except/else/finally, raising and defining exceptions, and why bare except: hides bugs including the KeyboardInterrupt you are pressing.
Pythonic Code and the Object Model
Classes, dunder methods, and properties
__init__, __repr__, __eq__, __hash__, and @property. A good __repr__ pays for itself the first time you debug a list of objects.
Iterators, context managers, and the protocols
Implement __iter__/__next__ and __enter__/__exit__. Duck typing means these protocols, not inheritance, are how Python composes.
Decorators and closures
Write decorators with functools.wraps, including ones that take arguments. You will meet them in every framework before you write one.
Dataclasses and named tuples
dataclasses with slots and frozen for lightweight, immutable models. Reach for these before writing __init__ by hand.
Standard library fluency
pathlib, collections (defaultdict, Counter, deque), itertools, functools, datetime with timezones, and json. Most "I need a library for this" moments are already solved here.
Method resolution order and multiple inheritance
How Python linearises the class hierarchy and what super() actually does in a diamond. Useful for reading framework code, rarely for writing your own.
Typing and Tooling
Type hints
Annotate functions and classes, then read the modern syntax: built-in generics (list[int]), the | union operator, Optional, Protocol for structural typing, and TypedDict.
A static type checker
Run mypy or pyright in CI. Start with a loose configuration on new code only; turning strict mode on across a large untyped codebase produces thousands of errors and gets abandoned.
Formatting and linting
Adopt ruff for both linting and formatting, or black plus ruff. The point is to stop discussing style in code review, so pick one and enforce it in CI.
Virtual environments
Understand why isolation is mandatory, and use one tool consistently: venv, uv, or Poetry. Installing into the system Python is how you break your operating system's tooling.
Testing
pytest fundamentals
Plain assert statements, test discovery, and running a subset with -k. pytest's assertion rewriting is why its failure output is readable.
Fixtures and parametrize
Fixtures for setup and teardown with the right scope, and @pytest.mark.parametrize to turn five near-identical tests into one. Watch out for function-scoped fixtures doing expensive work.
Mocking, and when not to
unittest.mock and monkeypatch, plus the judgement call: prefer passing a fake in through a parameter over patching a module path, because patched paths break when you move code.
Coverage, honestly
Use coverage to find untested branches, not as a target to hit. A hundred percent coverage of assertions that assert nothing is a metric, not a test suite.
Property-based testing
Hypothesis generates inputs you would not have thought of, and shrinks failures to a minimal case. Worth it for parsers, serialisers, and anything with tricky edge cases.
Concurrency and the GIL
The GIL, concretely
Only one thread executes Python bytecode at a time, so threads do not speed up CPU-bound work. They do help I/O-bound work, because the lock is released while waiting on I/O.
threading vs multiprocessing
The rule of thumb: threads for I/O-bound work, processes for CPU-bound work. concurrent.futures gives both the same interface, so switching is a one-line change.
asyncio
async/await, the event loop, asyncio.gather and TaskGroup. Learn the failure mode first: one blocking synchronous call inside an async function stalls the entire loop.
Async libraries and the ecosystem split
You cannot use requests inside asyncio and get concurrency; you need httpx or aiohttp. Every sync library has this problem, and mixing the two worlds is the usual source of mysterious slowness.
Free-threaded Python
Recent CPython versions offer an experimental build without the GIL. Worth following, and not worth depending on until your dependencies support it.
Building Real Applications
A web framework
FastAPI for APIs, where type hints drive validation and docs. Django when you want an ORM, admin, auth and migrations included. Flask when you want to assemble the pieces yourself.
Data validation at the boundary
Pydantic for untrusted input, dataclasses for trusted internals. The distinction matters: validation belongs where data enters the system, not everywhere.
Databases and an ORM
SQLAlchemy or the Django ORM, plus migrations with Alembic or Django migrations. Learn to read the SQL your ORM emits, and to recognise the N+1 pattern.
Background work
Celery with Redis, or a lighter option like RQ or Dramatiq. Anything slower than a request timeout belongs in a queue rather than a thread you spawned in a view.
HTTP clients and retries
httpx or requests with explicit timeouts, and retries with exponential backoff and jitter. A client with no timeout will eventually hang a worker forever.
Packaging and Distribution
pyproject.toml
The single modern configuration file: project metadata, dependencies, and tool settings. setup.py is legacy and you should not start there.
Lockfiles and reproducible installs
A lockfile pins the entire resolved dependency tree. uv.lock, poetry.lock, or pip-compile output all work. Without one, two installs a week apart give different code.
Publishing to PyPI
Build with python -m build, upload with twine, and test on TestPyPI first. Use a trusted publisher from CI rather than a long-lived API token.
Containerising Python
Multi-stage builds, installing from the lockfile, running as a non-root user, and avoiding the trap of copying the whole source before installing dependencies and busting the layer cache every build.
Compiled extensions
Cython, mypyc, or a Rust extension via PyO3 when profiling proves a hot loop is the bottleneck. Profile first, because this is rarely the answer.
Running Python in Production
Structured logging
Configure the logging module properly with levels and a JSON formatter, writing to stdout. print() is not logging, and it loses you severity, context and timestamps.
Profiling and finding the real bottleneck
cProfile for CPU, tracemalloc for memory, py-spy to profile a running process without restarting it. Measure before optimising, every time.
Configuration and secrets
Read configuration from the environment, validate it at startup so a missing variable fails immediately, and pull secrets from a secret manager rather than a committed .env file.
Connection pooling
Bound your database connections, and understand why serverless breaks this: hundreds of concurrent function instances each opening a connection will exhaust the database long before the compute limit.
WSGI and ASGI servers
gunicorn for WSGI, uvicorn for ASGI, often gunicorn managing uvicorn workers. Worker count times threads has to fit both your CPU allocation and your database connection limit.
Error tracking and tracing
Sentry for exceptions with context, and OpenTelemetry when you need to follow a request across services. A traceback without the request that caused it is half a bug report.
Comments
Was this useful?
Continue on this topic
The same subject, covered a different way from the roadmap above.
Code snippetPython Dataclass Patterns: Slots, Frozen Instances, and Field Validation
A Python snippet exploring advanced dataclass patterns for production code. Covers `__slots__` with dataclasses, frozen immutable instances, `__post_init__` validation, field factories, and comparison with Pydantic for data modeling.
QuizRuby on Rails: Convention Over Configuration
Test your knowledge of Ruby on Rails: MVC, Active Record and associations, migrations, routing, controllers and callbacks, ERB views, and the conventions that make Rails productive.
Case studyHardening a CI/CD Supply Chain to SLSA Level 3
How we made a build pipeline tamper-evident end to end: hermetic builds, KMS-signed provenance, and an admission gate that refuses anything it cannot verify.
- Cheatsheet
Python Virtual Environments Cheatsheet
A practical reference for venv, pip, requirements files, pyenv version switching, and pipx for isolated global tools.
You might also enjoy
More posts on similar topics

Node.js Developer Beginner to Expert
Node.js Developer Beginner to Expert This roadmap takes you from JavaScript foundations and the event loop through building APIs, working with databases, streams, testing, and security, and on to r

Full-Stack Developer Beginner to Expert
Full-Stack Developer Beginner to Expert This roadmap walks you from your first web page to shipping and operating a complete production application. Work the stages in order. Build the frontend fun

Backend Developer Beginner to Expert
Backend Developer Beginner to Expert This roadmap walks you from your first server-side program to designing scalable, secure systems. Work through the stages in order. Nail a language, the command

React Developer Beginner to Expert
React Developer Beginner to Expert This roadmap takes you from JavaScript prerequisites through JSX, hooks, and state management, and on to data fetching, meta-frameworks, performance, and testing.

Frontend Developer Beginner to Expert
Frontend Developer Beginner to Expert This roadmap walks you from absolute beginner to a strong, hireable frontend engineer. Work through the stages in order. The early ones build the mental model

Release Engineer Beginner to Expert
This roadmap takes you from release engineering principles and version control mastery through to advanced GitOps patterns and multi-account AWS delivery at scale. Each stage builds on the last. Treat
6 related posts