Python is the most popular programming language in the world and the top choice for web development, data science, automation, and AI engineering. This roadmap shows you exactly what to learn, in what order, and realistic timelines to go from zero to job-ready in 2025.
At a glance
| Phase | Topics | Time estimate |
|---|---|---|
| 1 | Python fundamentals | 4–6 weeks |
| 2 | Intermediate Python | 4–6 weeks |
| 3 | Object-oriented programming | 3–4 weeks |
| 4 | Git and the command line | 1–2 weeks |
| 5 | Web frameworks (Django or FastAPI) | 6–8 weeks |
| 6 | Databases and SQL | 4–6 weeks |
| 7 | APIs — REST and authentication | 3–4 weeks |
| 8 | Testing | 2–3 weeks |
| 9 | DevOps basics — Docker and CI/CD | 3–5 weeks |
| 10 | Choose a specialisation | 4–8 weeks |
| 11 | Portfolio projects and job search | 4–6 weeks |
| Total to first job | ~10–14 months |
Phase 1 — Python fundamentals (Weeks 1–6)
Learn the language itself before anything else. Python's clean syntax makes this faster than most languages.
Core syntax
# Variables and types
name = "Alice" # str
age = 30 # int
score = 9.5 # float
is_active = True # bool
# Input / output
user_input = input("Enter your name: ")
print(f"Hello, {user_input}!")
# Arithmetic
result = 10 // 3 # integer division → 3
remainder = 10 % 3 # modulo → 1
power = 2 ** 8 # exponentiation → 256
Control flow
# if / elif / else
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
else:
grade = "C"
# for loop with range
for i in range(5):
print(i) # 0 1 2 3 4
# while loop
count = 0
while count < 3:
count += 1
Functions
# Basic function
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# Multiple return values
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([3, 1, 4, 1, 5, 9])
Built-in data structures
| Structure | Example | Use case |
|---|---|---|
list |
[1, 2, 3] |
Ordered, mutable collection |
tuple |
(1, 2, 3) |
Ordered, immutable — coordinates, DB rows |
dict |
{"a": 1} |
Key-value pairs — configs, JSON |
set |
{1, 2, 3} |
Unique elements, fast membership test |
str |
"hello" |
Text — immutable sequence of characters |
Resources for Phase 1
- Official tutorial: docs.python.org/3/tutorial — free, authoritative
- Practice: Exercism Python track, LeetCode Easy problems
- Book: Automate the Boring Stuff with Python (free online) — practical from day 1
Phase 2 — Intermediate Python (Weeks 7–12)
Once you know the basics, these patterns separate hobbyists from professionals.
List comprehensions and generators
# List comprehension — concise, readable
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
# Dict comprehension
word_lengths = {word: len(word) for word in ["hello", "world"]}
# Generator — lazy evaluation, memory efficient
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
first_ten = [next(fib) for _ in range(10)]
File I/O and error handling
# Reading files safely
with open("data.txt", "r", encoding="utf-8") as f:
content = f.read()
# Writing files
with open("output.txt", "w") as f:
f.write("Hello, file!")
# Error handling
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Math error: {e}")
except (ValueError, TypeError) as e:
print(f"Type error: {e}")
finally:
print("Always runs — good for cleanup")
String manipulation
text = " Hello, World! "
text.strip() # "Hello, World!"
text.lower() # " hello, world! "
text.split(", ") # [" Hello", "World! "]
", ".join(["a", "b"]) # "a, b"
text.replace("o", "0") # " Hell0, W0rld! "
# f-strings (Python 3.6+) — prefer over .format()
name = "Alice"
age = 30
print(f"{name} is {age} years old") # simple
print(f"{3.14159:.2f}") # formatting → 3.14
print(f"{'left':<10}|{'right':>10}") # alignment
Working with modules and packages
# Standard library modules you'll use constantly
import os # file system, environment variables
import sys # command-line args, path manipulation
import json # parse and write JSON
import re # regular expressions
import datetime # dates and times
import pathlib # modern file paths (prefer over os.path)
import collections # Counter, defaultdict, namedtuple
import itertools # combinations, permutations, chain
import functools # lru_cache, reduce, partial
# Importing patterns
from pathlib import Path
from collections import Counter, defaultdict
from datetime import datetime, timedelta
Virtual environments (essential from day 1)
# Create a virtual environment
python -m venv venv
# Activate (Linux/Mac)
source venv/bin/activate
# Activate (Windows)
venv\Scripts\activate
# Install packages
pip install requests pandas
# Save dependencies
pip freeze > requirements.txt
# Install from requirements
pip install -r requirements.txt
Phase 3 — Object-oriented programming (Weeks 13–16)
OOP lets you model real-world concepts in code. Essential for larger projects.
Classes and inheritance
class Animal:
# Class variable (shared across all instances)
kingdom = "Animalia"
def __init__(self, name: str, sound: str):
# Instance variables
self.name = name
self._sound = sound # _ prefix = "private by convention"
def speak(self) -> str:
return f"{self.name} says {self._sound}"
def __repr__(self) -> str:
return f"Animal(name={self.name!r})"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Animal):
return NotImplemented
return self.name == other.name
class Dog(Animal):
def __init__(self, name: str, breed: str):
super().__init__(name, "Woof")
self.breed = breed
def fetch(self) -> str:
return f"{self.name} fetches the ball!"
# Override parent method
def speak(self) -> str:
return f"{super().speak()} (excitedly)"
dog = Dog("Rex", "Labrador")
print(dog.speak()) # Rex says Woof (excitedly)
print(dog.kingdom) # Animalia
Special (dunder) methods
| Method | Trigger | Use case |
|---|---|---|
__init__ |
MyClass() |
Constructor |
__str__ |
str(obj), print(obj) |
Human-readable string |
__repr__ |
repr(obj), REPL display |
Debug string, should be unambiguous |
__len__ |
len(obj) |
Custom length |
__eq__ |
obj1 == obj2 |
Equality comparison |
__lt__ |
obj1 < obj2 |
Less-than (enables sorting) |
__contains__ |
x in obj |
Membership test |
__iter__ |
for x in obj |
Iteration |
__enter__/__exit__ |
with obj: |
Context manager |
Dataclasses (modern Python)
from dataclasses import dataclass, field
@dataclass
class Point:
x: float
y: float
label: str = "point"
tags: list = field(default_factory=list)
def distance_from_origin(self) -> float:
return (self.x**2 + self.y**2) ** 0.5
p = Point(3.0, 4.0)
print(p.distance_from_origin()) # 5.0
print(p) # Point(x=3.0, y=4.0, label='point', tags=[])
Phase 4 — Git and the command line (Weeks 17–18)
Every professional Python developer uses Git daily.
Essential Git commands
| Command | What it does |
|---|---|
git init |
Initialise a repo |
git clone <url> |
Clone a remote repo |
git status |
See changed files |
git add <file> |
Stage a file |
git commit -m "msg" |
Create a commit |
git push |
Push to remote |
git pull |
Fetch and merge remote changes |
git branch <name> |
Create a branch |
git checkout -b <name> |
Create and switch to branch |
git merge <name> |
Merge branch |
git log --oneline |
Compact commit history |
Commit message convention
feat: add user authentication
fix: handle empty list in sort function
refactor: extract validation logic to utils
docs: update README with setup instructions
test: add tests for payment module
Command line basics
ls -la # list files with details
cd projects/myapp # change directory
mkdir src # make directory
rm -rf build/ # remove directory (careful!)
cp file.py backup/ # copy file
mv old.py new.py # rename/move file
cat config.json # print file
grep -r "TODO" src/ # search text in files
python app.py # run Python script
pip install flask # install package
Phase 5 — Web frameworks (Weeks 19–26)
Python has two dominant web frameworks. Pick one and go deep.
Django vs FastAPI
| Dimension | Django | FastAPI |
|---|---|---|
| Philosophy | "Batteries included" — auth, admin, ORM built in | Minimal core, add what you need |
| Best for | Full web apps, admin panels, CMS | APIs, microservices, ML serving |
| ORM | Django ORM (built-in) | SQLAlchemy (external) |
| Speed (req/s) | ~5,000 | ~50,000+ |
| Learning curve | Steeper — more concepts | Easier entry |
| Async support | Partial (Django 4.1+) | First-class native async |
| Admin panel | Built-in, powerful | None built-in |
| Job market 2025 | Very strong | Fast growing |
Choose Django if you're building a full web app with auth, admin, or CMS needs.
Choose FastAPI if you're building REST APIs, ML model serving, or microservices.
Django quick start
# models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
published_at = models.DateTimeField(auto_now_add=True)
is_published = models.BooleanField(default=False)
class Meta:
ordering = ["-published_at"]
def __str__(self):
return self.title
# views.py
from django.shortcuts import get_object_or_404, render
from .models import Article
def article_list(request):
articles = Article.objects.filter(is_published=True).select_related()
return render(request, "articles/list.html", {"articles": articles})
def article_detail(request, pk):
article = get_object_or_404(Article, pk=pk, is_published=True)
return render(request, "articles/detail.html", {"article": article})
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("articles/", views.article_list, name="article-list"),
path("articles/<int:pk>/", views.article_detail, name="article-detail"),
]
FastAPI quick start
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
from typing import Optional
app = FastAPI(title="My API", version="1.0.0")
# Pydantic model for request/response validation
class ArticleCreate(BaseModel):
title: str
body: str
class Article(BaseModel):
id: int
title: str
body: str
class Config:
from_attributes = True
# In-memory store (replace with DB in production)
articles: list[Article] = []
next_id = 1
@app.get("/articles", response_model=list[Article])
async def get_articles():
return articles
@app.post("/articles", response_model=Article, status_code=201)
async def create_article(data: ArticleCreate):
global next_id
article = Article(id=next_id, **data.model_dump())
articles.append(article)
next_id += 1
return article
@app.get("/articles/{article_id}", response_model=Article)
async def get_article(article_id: int):
for article in articles:
if article.id == article_id:
return article
raise HTTPException(status_code=404, detail="Article not found")
Phase 6 — Databases and SQL (Weeks 27–32)
Almost every Python app needs persistent data storage.
SQL fundamentals
-- Create table
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- CRUD operations
INSERT INTO users (email) VALUES ('alice@example.com');
SELECT * FROM users WHERE email LIKE '%@example.com';
UPDATE users SET email = 'new@example.com' WHERE id = 1;
DELETE FROM users WHERE id = 1;
-- Joins
SELECT u.email, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.email
HAVING COUNT(o.id) > 0
ORDER BY order_count DESC;
Python database tools
| Tool | Category | When to use |
|---|---|---|
psycopg2 |
Driver | Raw SQL with PostgreSQL |
SQLAlchemy |
ORM + Core | Most Python projects — flexible |
Django ORM |
ORM | Django projects |
SQLModel |
ORM | FastAPI projects (wraps SQLAlchemy) |
Alembic |
Migrations | Database schema versioning |
Redis (redis-py) |
Cache | Sessions, queues, rate limiting |
SQLAlchemy example
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.orm import DeclarativeBase, Session
from datetime import datetime
engine = create_engine("postgresql+psycopg2://user:pass@localhost/mydb")
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String(255), unique=True, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
Base.metadata.create_all(engine)
# CRUD
with Session(engine) as session:
user = User(email="alice@example.com")
session.add(user)
session.commit()
users = session.query(User).filter(User.email.like("%@example.com")).all()
Phase 7 — APIs and authentication (Weeks 33–36)
REST API patterns
# Standard API response envelope
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Any, Optional
class APIResponse(BaseModel):
success: bool
data: Optional[Any] = None
error: Optional[str] = None
# JWT authentication with FastAPI
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
SECRET_KEY = "your-secret-key" # Use env var in production
ALGORITHM = "HS256"
security = HTTPBearer()
def create_token(user_id: int) -> str:
payload = {"sub": str(user_id), "exp": ...}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def get_current_user(creds: HTTPAuthorizationCredentials = Depends(security)):
token = creds.credentials
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload["sub"]
Making HTTP requests
import httpx # prefer over requests for async support
import requests # fine for scripts and sync code
# Sync request
response = requests.get("https://api.github.com/users/python")
data = response.json()
# Async request (use in FastAPI/async code)
async def fetch_user(username: str):
async with httpx.AsyncClient() as client:
response = await client.get(f"https://api.github.com/users/{username}")
response.raise_for_status()
return response.json()
Phase 8 — Testing (Weeks 37–39)
Untested Python code is a liability. The ecosystem makes testing easy.
pytest basics
# tests/test_calculator.py
import pytest
from myapp.calculator import add, divide
def test_add_positive_numbers():
assert add(2, 3) == 5
def test_add_negative_numbers():
assert add(-1, -1) == -2
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
divide(10, 0)
# Parametrised tests
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
])
def test_add_parametrised(a, b, expected):
assert add(a, b) == expected
# Fixtures
@pytest.fixture
def sample_user():
return {"id": 1, "email": "test@example.com"}
def test_user_has_email(sample_user):
assert "@" in sample_user["email"]
Testing levels
| Level | Tool | What to test | Speed |
|---|---|---|---|
| Unit | pytest | Pure functions, classes | Very fast |
| Integration | pytest + real DB | Database queries, ORM | Medium |
| API | pytest + httpx/TestClient | Endpoints end-to-end | Medium |
| E2E | Playwright | Full user flows in browser | Slow |
Django test client
from django.test import TestCase, Client
from django.contrib.auth.models import User
class ArticleViewTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user("testuser", password="pass")
def test_article_list_returns_200(self):
response = self.client.get("/articles/")
self.assertEqual(response.status_code, 200)
def test_create_article_requires_auth(self):
response = self.client.post("/articles/create/", {"title": "Test"})
self.assertRedirects(response, "/login/?next=/articles/create/")
Phase 9 — DevOps basics (Weeks 40–44)
Docker for Python apps
# Dockerfile — multi-stage build
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY . .
# Don't run as root
RUN adduser --disabled-password appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# docker-compose.yml
version: "3.9"
services:
api:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
healthcheck:
test: ["CMD", "pg_isready", "-U", "user"]
interval: 5s
retries: 5
CI/CD with GitHub Actions
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: testpass
options: >-
--health-cmd pg_isready
--health-interval 10s
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r requirements.txt
- run: pytest --cov=myapp --cov-report=term-missing
- run: ruff check .
- run: mypy myapp/
Phase 10 — Choose a specialisation (Weeks 45–52)
After the core skills, Python branches into distinct career tracks.
| Specialisation | Key technologies | Typical salary (US, 2025) |
|---|---|---|
| Web backend | Django, FastAPI, PostgreSQL, Redis, Celery | $90k–$150k |
| Data engineering | Spark, Airflow, dbt, Kafka, Snowflake | $110k–$170k |
| Data science / ML | pandas, scikit-learn, PyTorch, MLflow | $100k–$160k |
| AI / LLM engineering | LangChain, vector DBs, OpenAI API, RAG | $130k–$200k+ |
| DevOps / automation | boto3, Ansible, Terraform scripts | $100k–$160k |
| Security / scripting | pwntools, Scapy, automation scripts | $90k–$140k |
Quick tools for each track
Web backend: django, fastapi, celery, redis, httpx, alembic
Data engineering: pyspark, apache-airflow, dbt-core, kafka-python
Data science: pandas, numpy, scikit-learn, matplotlib, jupyter
AI engineering: openai, langchain, chromadb, anthropic, sentence-transformers
Full technology map
Python Developer
├── Language core
│ ├── Built-in types (str, list, dict, set, tuple)
│ ├── Comprehensions and generators
│ ├── Decorators and context managers
│ ├── Type hints + mypy
│ └── Standard library (os, sys, json, re, pathlib, datetime)
│
├── Package management
│ ├── pip + virtualenv / venv
│ └── uv (modern, fast replacement for pip)
│
├── Web frameworks
│ ├── Django (full-stack)
│ └── FastAPI (API-first, async)
│
├── Databases
│ ├── PostgreSQL + psycopg2/asyncpg
│ ├── SQLAlchemy ORM + Alembic
│ └── Redis + redis-py
│
├── Testing
│ ├── pytest + pytest-cov
│ ├── unittest.mock
│ └── Faker (test data)
│
├── Code quality
│ ├── ruff (linter + formatter, replaces flake8/black)
│ ├── mypy (type checker)
│ └── pre-commit hooks
│
└── Infrastructure
├── Docker + docker-compose
├── GitHub Actions CI/CD
└── Cloud (AWS Lambda / GCP Cloud Run / Render / Railway)
Realistic timeline
| Month | What you can do | Goal |
|---|---|---|
| 1–2 | Write scripts, solve Exercism exercises | Fundamentals solid |
| 3–4 | Build CLI tools, manipulate files/APIs | Intermediate Python confident |
| 5–6 | Build your first Django or FastAPI app | First web project deployed |
| 7–8 | Add PostgreSQL, auth, tests | Full working API |
| 9–10 | Docker, CI/CD, pick a specialisation | Production-ready mindset |
| 11–12 | Build portfolio projects, apply to jobs | Job-ready |
| 12–14 | Interview prep + actual applications | First Python job |
Portfolio projects
| Project | Skills demonstrated | Level |
|---|---|---|
| URL shortener CLI | argparse, file I/O, HTTP requests | Beginner |
| Personal budget tracker | CSV/JSON, data manipulation, reports | Beginner |
| REST API (FastAPI) | CRUD, Pydantic, auth, tests | Intermediate |
| Django blog with auth | ORM, templates, forms, sessions | Intermediate |
| Web scraper + dashboard | requests, BeautifulSoup, pandas, charts | Intermediate |
| Async task queue | Celery, Redis, background jobs | Advanced |
| ML model API | scikit-learn, FastAPI serving, Docker | Advanced |
Python roles and salaries
| Role | Core skills | US salary 2025 |
|---|---|---|
| Python developer | Django/FastAPI, PostgreSQL, REST APIs | $85k–$130k |
| Backend engineer | Distributed systems, async, performance | $100k–$160k |
| Data engineer | ETL pipelines, Airflow, Spark, SQL | $110k–$170k |
| Data scientist | pandas, sklearn, model building, statistics | $100k–$155k |
| ML engineer | PyTorch, MLOps, model serving, deployment | $120k–$180k |
| AI engineer | LLMs, RAG, embeddings, agent frameworks | $130k–$200k+ |
Common mistakes
| Mistake | What goes wrong | Fix |
|---|---|---|
| Skipping virtual environments | Conflicting packages break projects | Always use venv or uv |
Using import * |
Hidden name collisions, hard to debug | Explicit imports only |
| Mutable default arguments | def f(lst=[]) — same list across all calls |
Use None + if lst is None: lst = [] |
Catching bare except: |
Swallows KeyboardInterrupt, hides bugs |
Catch specific exceptions |
Using == to check None |
Overrideable — subtly wrong | Use is None / is not None |
| Ignoring type hints | Runtime surprises, hard refactoring | Add mypy from the start |
| No tests | Regressions pile up, refactoring is scary | Write pytest from day one |
| Blocking calls in async code | time.sleep() in async freezes everything |
Use asyncio.sleep(), httpx.AsyncClient |
Frequently asked questions
How long does it take to get a Python job?
With consistent daily practice (2–3 hours), most people reach job-ready level in 10–14 months. A CS degree shortens this; starting with zero programming experience lengthens it slightly.
Should I learn Django or FastAPI first?
If you want web development jobs in 2025, Django is still the safer bet — more jobs, more tutorials, built-in everything. If you're targeting API development or ML serving, FastAPI has better async support and growing adoption. Many developers learn both.
Do I need to know data science to be a Python developer?
No. Data science is one of many Python tracks. Pure web backend Python roles exist in abundance and don't require pandas or sklearn.
Is Python good for mobile apps?
No. Use Flutter (Dart) or React Native for cross-platform mobile. Python is not used for mobile development in 2025.
What's the difference between Python 2 and Python 3?
Python 2 reached end-of-life in 2020. Learn Python 3 — Python 2 is irrelevant for new projects.
What code editor should I use?
VS Code with the Pylance extension is the most popular choice in 2025. PyCharm (JetBrains) is excellent for larger Django projects with paid Professional features. Both are fine — pick one and learn it well.