Python

Python From Zero to Interview-Ready

A complete guide to learning Python and cracking the coding interview


How to use this guide

This is long on purpose. It is meant to be a reference you return to, not something you read once.

There are four parts:

Part What it covers Who it's for
1. Introduction to Python What Python is, how it works, what it's good and bad at, how to set it up Absolute beginners
2. Learning Python A 12-week phased roadmap from syntax to writing real code Beginners → intermediate
3. The interview roadmap Data structures, algorithms, patterns, and a week-by-week study plan Anyone job hunting
4. Interview execution How to actually behave in a 45-minute interview Anyone with interviews booked

If you already know Python, skip to Part 3. If you know Python and DSA, skip to Part 4 — that's where most people with strong fundamentals still lose offers.

One honest expectation-setting note before we start: going from "never programmed" to "passing FAANG-level interviews" takes most people 8–14 months of consistent effort, not six weeks. If you already program in another language, 3–5 months is realistic. Anyone promising faster is selling something.


Part 1 — Introduction to Python

1.1 What Python actually is

Python is a high-level, interpreted, dynamically typed, garbage-collected, general-purpose programming language. That's a mouthful, so let's take it apart:

  • High-level — you don't manage memory, pointers, or CPU registers. You write names.append("Ada") instead of allocating heap blocks.
  • Interpreted — you run source files directly (python app.py) without a separate compile step. (Technically Python is compiled, to bytecode — more on that below.)
  • Dynamically typed — a variable's type is checked at runtime, not declared up front. x = 5 then x = "five" is legal.
  • Garbage-collected — memory is reclaimed automatically when objects are no longer referenced.
  • General-purpose — not a niche tool. It runs web backends, machine learning models, data pipelines, automation scripts, embedded devices, and scientific research.

It was created by Guido van Rossum and released in 1991. The name comes from Monty Python's Flying Circus, not the snake — which is why so much Python documentation historically used "spam" and "eggs" as example variables.

The current major version is Python 3. Python 2 is dead (end of life: January 2020). If a tutorial has print "hello" without parentheses, close the tab.

1.2 Why Python is worth learning

It reads like pseudocode. Compare summing a list of numbers.

Java:

int total = 0;
for (int i = 0; i < numbers.length; i++) {
    total += numbers[i];
}

Python:

total = sum(numbers)

That gap compounds. Less code means fewer bugs, faster iteration, and — importantly for you — less mental overhead during a timed interview when you're already stressed. This is the single biggest reason Python dominates as an interview language.

The ecosystem is unmatched in breadth. There are over 500,000 packages on PyPI, the Python Package Index. Whatever you need — HTTP requests, PDF parsing, neural networks, web scraping, Excel manipulation, cryptography — someone has already built a good library.

It is the default language of AI and data. PyTorch, TensorFlow, JAX, scikit-learn, pandas, NumPy, and effectively every major ML framework has Python as its primary interface. If you want to work anywhere near machine learning, Python is not optional.

Career demand is broad and durable. Python consistently sits in the top three of the TIOBE index, Stack Overflow's developer survey, and GitHub's language rankings. It is used at Google, Netflix, Instagram (one of the largest Django deployments in the world), Spotify, Dropbox, NASA, and JPMorgan. This matters for job security: you're not betting your career on a trend.

The learning curve is gentle but the ceiling is high. You can write something useful in week one. You can also write a distributed system, a compiler, or a game engine in it. Few languages give you both.

1.3 Where Python is honestly the wrong choice

Being clear-eyed about this makes you a better engineer, and interviewers notice when you can discuss tradeoffs.

  • Raw CPU speed. Python is roughly 10–100× slower than C for tight numeric loops. The workaround is that the heavy lifting in libraries like NumPy is written in C or Fortran, so you're often orchestrating fast code rather than running slow code. But a pure-Python physics engine is a bad idea.
  • True CPU parallelism in one process. Historically, CPython's Global Interpreter Lock (GIL) allowed only one thread to execute Python bytecode at a time. Threads still help with I/O-bound work (waiting on network or disk), but for CPU-bound work you reach for multiprocessing or a native extension. Python 3.13 introduced an experimental free-threaded build that removes the GIL, and 3.14 continued that work, but as of now most production deployments still run with the GIL.
  • Mobile apps. No serious native iOS/Android story. Use Swift, Kotlin, or a cross-platform framework.
  • Front-end browser code. JavaScript owns that. (Tools like Pyodide compile Python to WebAssembly, but it's niche.)
  • Very small memory footprints. A Python object carries significant overhead compared to a C struct. Microcontrollers use MicroPython or C.
  • Large codebases without discipline. Dynamic typing that feels liberating in a 200-line script becomes dangerous in 200,000 lines. Mature teams solve this with type hints and mypy, but it's opt-in, not enforced.

1.4 How Python runs your code

Understanding this makes debugging and performance conversations much easier, and it's a common "do you actually know your language" interview question.

your_file.py
     │
     ▼  1. Lexing & parsing → Abstract Syntax Tree
     │
     ▼  2. Compilation → bytecode (cached in __pycache__/*.pyc)
     │
     ▼  3. Execution → CPython Virtual Machine evaluates bytecode
     │
     ▼  Output

So Python is compiled — just to bytecode for a virtual machine, not to native machine code. Those __pycache__ folders you see are compilation caches. Deleting them is harmless; they regenerate.

You can inspect bytecode yourself:

import dis

def add(a, b):
    return a + b

dis.dis(add)

"CPython" is the reference implementation — the one you download from python.org, written in C. Alternatives exist and are worth knowing by name:

Implementation What's different When you'd use it
CPython The standard Almost always
PyPy JIT compiler, often 4–10× faster Long-running CPU-bound pure-Python code
MicroPython Tiny footprint Microcontrollers, embedded
Jython / IronPython Runs on JVM / .NET Legacy interop (both lag badly on version support)

1.5 Everything is an object

This is the mental model that unlocks Python. Integers, strings, functions, classes, modules, even types themselves are objects with identity, a type, and attributes.

x = 42
print(type(x))            # <class 'int'>
print(x.bit_length())     # 6  — integers have methods
print((42).__add__(8))    # 50 — the + operator calls a method

def greet():
    return "hi"

print(type(greet))        # <class 'function'>
greet.author = "me"       # functions can hold attributes
print(greet.author)       # me

functions = [greet, len, str.upper]   # functions live in data structures

A consequence worth internalizing early: variables are names bound to objects, not boxes holding values. Assignment binds a name; it doesn't copy.

a = [1, 2, 3]
b = a           # b and a name the SAME list
b.append(4)
print(a)        # [1, 2, 3, 4]  ← surprised? this trips up everyone once

c = a.copy()    # now c is a separate list (shallow copy)
c.append(5)
print(a)        # [1, 2, 3, 4] — unchanged

This single concept explains a huge share of beginner bugs and shows up in interviews as questions about mutability and aliasing.

1.6 Setting up your environment

Install Python. Get it from python.org or use a version manager. On macOS, do not use the system Python that ships with the OS — install your own via Homebrew (brew install python) or pyenv. On Windows, check "Add Python to PATH" during install. On Linux it's likely already there; verify with:

python3 --version    # should print 3.11 or newer

Learn virtual environments on day one. Do not skip this. A virtual environment is an isolated folder of packages for one project, so Project A's requirement of pandas 1.5 doesn't break Project B's need for pandas 2.2.

python3 -m venv .venv              # create
source .venv/bin/activate          # activate (macOS/Linux)
.venv\Scripts\activate             # activate (Windows)

pip install requests               # installs into .venv only
pip freeze > requirements.txt      # record dependencies
pip install -r requirements.txt    # reproduce elsewhere

deactivate                         # exit

Add .venv/ to your .gitignore. Never commit a virtual environment.

Modern alternatives worth knowing: uv (extremely fast, written in Rust, increasingly the default in new projects), Poetry (dependency resolution and packaging), and conda (heavier, popular in data science because it manages non-Python binaries too).

Pick an editor. VS Code with the Python and Pylance extensions is the mainstream free choice. PyCharm is more powerful out of the box, especially for debugging and refactoring. Jupyter notebooks are excellent for data exploration and terrible for building software — use them to explore, not to structure a codebase.

Configure these three tools early. They make you look and feel like a professional:

pip install ruff mypy pytest
ruff check .          # linting + formatting, very fast
mypy app.py           # static type checking
pytest                # run tests

1.7 Your first tour of the language

Rather than a "hello world" you'll forget, here's a compressed tour of the syntax you'll use 90% of the time. Type it out — don't copy-paste. Motor memory matters.

# ---------- Variables and types ----------
name = "Ada"                  # str
age = 36                      # int
height = 1.68                 # float
is_engineer = True            # bool
nothing = None                # NoneType

# f-strings: the modern way to format
print(f"{name} is {age} years old, {height:.1f}m tall")
print(f"{age = }")            # debugging shortcut → age = 36

# ---------- Control flow ----------
if age >= 18:
    status = "adult"
elif age >= 13:
    status = "teen"
else:
    status = "child"

# Indentation IS the syntax. 4 spaces. No braces. Be consistent.

for i in range(3):            # 0, 1, 2
    print(i)

for index, char in enumerate("abc"):
    print(index, char)

n = 5
while n > 0:
    n -= 1
    if n == 2:
        continue              # skip rest of this iteration
    if n == 1:
        break                 # exit loop entirely

# ---------- The four core collections ----------
nums   = [3, 1, 2]                    # list  — ordered, mutable
point  = (4, 5)                       # tuple — ordered, immutable
unique = {1, 2, 3}                    # set   — unordered, no duplicates
ages   = {"Ada": 36, "Alan": 41}      # dict  — key → value

nums.append(4)
nums.sort()                            # in place → [1, 2, 3, 4]
print(sorted(nums, reverse=True))      # returns new list → [4, 3, 2, 1]

x, y = point                           # unpacking
first, *rest = [1, 2, 3, 4]            # first=1, rest=[2, 3, 4]

for key, value in ages.items():
    print(key, value)

# ---------- Comprehensions: the Python signature move ----------
squares  = [n * n for n in range(10)]
evens    = [n for n in range(20) if n % 2 == 0]
lookup   = {word: len(word) for word in ["hi", "there"]}
distinct = {c for c in "mississippi"}
lazy     = (n * n for n in range(10**9))   # generator: computed on demand

# ---------- Functions ----------
def area(width, height=1, *args, **kwargs):
    """Docstrings go here. Triple quotes."""
    return width * height

def stats(values: list[int]) -> tuple[int, float]:   # type hints: optional, valuable
    return sum(values), sum(values) / len(values)

total, mean = stats([1, 2, 3])

double = lambda n: n * 2               # anonymous function; use sparingly

# ---------- Errors ----------
try:
    result = 10 / 0
except ZeroDivisionError as exc:
    print(f"caught: {exc}")
except (TypeError, ValueError):
    print("bad input")
else:
    print("no exception occurred")
finally:
    print("always runs — cleanup goes here")

# ---------- Classes ----------
class Account:
    interest_rate = 0.02                       # class attribute, shared

    def __init__(self, owner, balance=0):      # constructor
        self.owner = owner                     # instance attributes
        self._balance = balance                # leading _ means "internal, please don't touch"

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("amount must be positive")
        self._balance += amount
        return self._balance

    @property
    def balance(self):                         # accessed as obj.balance, not obj.balance()
        return self._balance

    def __repr__(self):                        # what you see in the debugger
        return f"Account({self.owner!r}, {self._balance})"

acct = Account("Ada", 100)
acct.deposit(50)
print(acct.balance, acct)      # 150 Account('Ada', 150)

# ---------- Context managers: guaranteed cleanup ----------
with open("data.txt", "w") as f:
    f.write("hello")
# file is closed automatically, even if an exception was raised

If you understand every line above, you know enough syntax to start solving real problems. Everything else is libraries and practice.

1.8 The Zen of Python

Run import this in a Python shell. You get a short poem of design principles by Tim Peters. The lines that matter most in practice:

  • Readability counts. Code is read far more often than it's written.
  • Explicit is better than implicit. Don't be clever at the reader's expense.
  • Simple is better than complex. Reach for the boring solution first.
  • There should be one — and preferably only one — obvious way to do it. This is why the community converges on shared idioms so strongly.
  • Errors should never pass silently. A bare except: pass is almost always a bug you haven't found yet.

"Pythonic" code means code that follows these idioms. It's not pedantry — an interviewer watching you write for i in range(len(arr)): print(arr[i]) instead of for item in arr: print(item) will quietly downgrade their read of your fluency.


Part 2 — How to Actually Learn Python

2.1 The learning principle that matters more than any resource

You cannot learn programming by watching. You learn it by producing code that doesn't work and then making it work.

Watching a tutorial creates the feeling of understanding — the instructor's code runs, it makes sense, you nod along. That feeling is fluency in reading, not writing. Then you open a blank file and freeze. This is the single most common failure mode in self-taught programming, and it has a name: tutorial hell.

The fix is uncomfortable and simple: for every 20 minutes of consuming, spend 40 minutes producing. Specifically:

  1. Watch/read a concept once. No re-watching.
  2. Close the tutorial. Rebuild the example from memory. You will fail. Good — the struggle is where learning happens.
  3. Break it deliberately. Change something and predict the outcome before running it. Wrong predictions are the most valuable data you'll get about your own mental model.
  4. Extend it beyond what was taught. The tutorial made a to-do list? Add due dates, sorting, and persistence to a file.
  5. Explain it out loud as if teaching someone. Gaps become audible immediately.

Two techniques with strong evidence behind them are worth building in explicitly. Spaced repetition: revisit a concept after 1 day, 3 days, 1 week, 3 weeks. Retrieval practice: instead of re-reading notes, close them and write down everything you remember. Both feel less productive than re-reading, and both work substantially better.

2.2 The 12-week Python roadmap

This assumes ~10–12 hours per week. Adjust the calendar, not the sequence. Do not move to the next phase until you can build the checkpoint project without looking anything up except official docs.


Phase 0 — Setup (2–3 hours, do it once)

Install Python, create a virtual environment, install VS Code, write and run one file, initialize a Git repository, push it to GitHub.

That last part matters. Start committing from day one, even for throwaway practice files. Twelve weeks from now you'll have a visible history of consistent work, and that history is a genuinely useful signal to recruiters.

Checkpoint: you can create a fresh project with a venv, install a package, commit, and push without a guide.


Phase 1 — Core syntax (Weeks 1–3)

Week Topics
1 Variables, types, operators, input()/print(), f-strings, if/elif/else, while, for, range, break/continue
2 Lists and list methods, tuples, strings and string methods, slicing, indexing, nested loops
3 Dictionaries, sets, functions, parameters vs arguments, default args, *args/**kwargs, return values, scope, import

Concepts people consistently get wrong here — slow down on these:

  • Slicing. arr[start:stop:step], stop is exclusive, negatives count from the end. arr[::-1] reverses. arr[:] copies. Master it now; you'll use it in every interview.
  • Mutable vs immutable. Lists, dicts, and sets can be changed in place. Strings, tuples, ints, and frozensets cannot. This determines what can be a dict key (only hashable/immutable things).
  • In-place vs returning. list.sort() returns None and mutates; sorted(list) returns a new list. list.append() mutates. Confusing these produces AttributeError: 'NoneType' object has no attribute... — the single most common beginner traceback.
  • The mutable default argument trap.
def bad(item, basket=[]):        # the SAME list is reused across every call
    basket.append(item)
    return basket

print(bad("a"))    # ['a']
print(bad("b"))    # ['a', 'b']  ← almost never what you wanted

def good(item, basket=None):     # the correct pattern
    if basket is None:
        basket = []
    basket.append(item)
    return basket

Checkpoint projects — build all three:

  1. Number guessing game with input validation and a retry loop.
  2. Command-line to-do list with add/remove/list/mark-complete, held in memory.
  3. Text statistics tool that takes a paragraph and reports word count, unique words, the five most common words, and average word length.

Phase 2 — Idiomatic Python and data structures (Weeks 4–6)

Week Topics
4 Comprehensions (list/dict/set), generators and yield, enumerate, zip, any/all, min/max with key=
5 Sorting deeply (key=, stability, multi-key, reverse), lambdas, map/filter vs comprehensions, unpacking, */** splatting
6 Nested data structures, JSON, reading/writing files, pathlib, CSV, exceptions in depth, custom exception classes

This is the phase where you stop writing Java-with-Python-syntax and start writing Python. Concretely:

# Not Pythonic                        # Pythonic
i = 0                                 for item in items:
while i < len(items):                     print(item)
    print(items[i])
    i += 1

result = []                           result = [x * 2 for x in nums if x > 0]
for x in nums:
    if x > 0:
        result.append(x * 2)

if len(items) > 0:                    if items:

if x == None:                         if x is None:

for i in range(len(names)):           for i, name in enumerate(names):
    print(i, names[i])

for i in range(len(a)):               for x, y in zip(a, b):
    print(a[i], b[i])

temp = a; a = b; b = temp             a, b = b, a

Generators deserve special attention because they're both idiomatic and genuinely useful for memory efficiency:

def read_large_file(path):
    """Yields one line at a time — never loads the whole file into memory."""
    with open(path) as f:
        for line in f:
            yield line.strip()

# Nothing is read until you iterate
for line in read_large_file("10gb.log"):
    if "ERROR" in line:
        print(line)

Checkpoint projects:

  1. Expense tracker that persists to JSON, supports categories, and reports monthly totals sorted by amount.
  2. Log file analyzer that streams a large file with a generator and reports error counts by type and hour.
  3. CSV → report tool that reads a messy CSV, cleans it, and writes a summary CSV. (Handle missing values and bad rows without crashing.)

Phase 3 — Object-oriented Python and structure (Weeks 7–9)

Week Topics
7 Classes, __init__, instance vs class attributes, methods, @property, @staticmethod, @classmethod, __repr__/__str__
8 Inheritance, composition, super(), method resolution order, dunder methods (__len__, __eq__, __iter__, __hash__), dataclasses, Enum
9 Modules and packages, if __name__ == "__main__":, project layout, decorators, functools, closures, type hints and mypy

A framing that helps: classes are for bundling state with the behavior that operates on it. If your class has no state — just methods that take everything as arguments — you wanted functions in a module.

dataclasses remove most OOP boilerplate and are what modern Python actually uses:

from dataclasses import dataclass, field

@dataclass
class Task:
    title: str
    priority: int = 3
    tags: list[str] = field(default_factory=list)   # note: NOT tags: list = []
    done: bool = False

    def complete(self) -> None:
        self.done = True

t = Task("Write article", priority=1, tags=["writing"])
print(t)          # Task(title='Write article', priority=1, tags=['writing'], done=False)
# __init__, __repr__, and __eq__ are generated for you

Decorators look like magic until you see that they're just functions that take a function and return a function:

import functools, time

def timed(func):
    @functools.wraps(func)                 # preserves the original name and docstring
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@timed                                     # equivalent to: slow = timed(slow)
def slow():
    time.sleep(0.5)

slow()      # slow took 0.5004s

Checkpoint project: a library management systemBook, Member, and Library classes; borrowing with due dates and limits; custom exceptions (BookUnavailableError); JSON persistence; a proper package layout with __init__.py; and type hints throughout that pass mypy.


Phase 4 — Professional practice (Weeks 10–12)

Week Topics
10 Testing with pytest, fixtures, parametrized tests, mocking, test-driven development, coverage
11 Standard library depth: collections, itertools, datetime, re, os/sys, argparse, logging, json, random
12 Git workflow (branches, merges, pull requests), reading other people's code, debugging with pdb/breakpoints, virtual environments and dependency management, ruff + mypy in CI

Testing is the skill that most separates hobbyists from employable engineers, and it's the one self-taught developers skip most often. Learn it here:

# test_calculator.py
import pytest
from calculator import divide

def test_divides_normally():
    assert divide(10, 2) == 5

@pytest.mark.parametrize("a,b,expected", [(6, 3, 2), (-6, 3, -2), (0, 5, 0)])
def test_various_inputs(a, b, expected):
    assert divide(a, b) == expected

def test_raises_on_zero():
    with pytest.raises(ZeroDivisionError):
        divide(1, 0)

Also learn logging and delete your print() debugging habit:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
log = logging.getLogger(__name__)

log.info("Processing %d records", len(records))     # lazy formatting — no f-string
log.warning("Retrying after failure")
log.exception("Unrecoverable")                       # includes the full traceback

Checkpoint project: take your Phase 3 library system and add a full pytest suite above 80% coverage, an argparse CLI, structured logging, a pyproject.toml, a README with usage examples, and a GitHub Actions workflow that runs ruff, mypy, and pytest on every push. This project alone is a credible portfolio piece.


Phase 5 — Pick a specialization (Month 4+)

You now know Python. Depth comes from applying it to a domain. Pick one — spreading across three gets you hired for none.

Track Core stack Build this
Backend / web FastAPI or Django, PostgreSQL, SQLAlchemy, Redis, Docker, REST/auth A deployed API with authentication, a database, tests, and Docker
Data / analytics pandas, NumPy, Polars, SQL, matplotlib/Plotly, Jupyter, dbt An end-to-end analysis of a real messy dataset with a written conclusion
ML / AI scikit-learn, PyTorch, Hugging Face, MLflow A trained model served behind an API, with honest evaluation metrics
Automation / DevOps requests, Playwright, Celery, Airflow, boto3, Bash, CI/CD A scheduled pipeline that scrapes, transforms, and reports on real data
Data engineering Spark/PySpark, Kafka, Airflow, warehouses A batch + streaming pipeline with orchestration and data quality checks

2.3 Where to learn from

Curated over comprehensive — three great resources finished beat fifteen bookmarked.

Free and genuinely excellent:

  • The official Python Tutorial at docs.python.org. Underrated. Read it once you know basics; it's clearer than most paid courses.
  • Automate the Boring Stuff with Python (Al Sweigart) — free online, best possible starting point for absolute beginners because every chapter produces something useful.
  • Real Python — deep, accurate articles on specific topics. Excellent for "how does X actually work."
  • CS50P (Harvard's Introduction to Programming with Python) — free, rigorous, well-taught, with real problem sets.
  • Fluent Python (Luciano Ramalho) — not free, but the definitive intermediate→advanced book. Read it in month 4+, not month 1.

Practice platforms:

  • Exercism — Python track with human mentorship on your submissions. The feedback on idiom is uniquely valuable.
  • Codewars / HackerRank — quick syntax reps.
  • Advent of Code — annual puzzle set; excellent for building problem-solving stamina.
  • LeetCode / NeetCode — for interviews specifically. See Part 3.

A note on AI assistants: they are extraordinary learning tools and a genuine trap. Used well — "explain why this fails," "what's the Pythonic version of this," "quiz me on generators" — they compress months of learning. Used badly — "write my project" — they produce a portfolio you cannot defend in an interview and skills you don't have. My suggested rule: write it yourself first, then ask for a critique. And never let an AI write code you couldn't explain line by line to an interviewer, because you will be asked to.

2.4 The six mistakes that stall most learners

  1. Collecting resources instead of finishing one. Pick a course. Finish it. Then evaluate.
  2. Skipping the hard chapters — usually OOP, decorators, and testing. These are exactly the chapters that separate you from other candidates.
  3. Never building anything unprompted. Projects that come from your own annoyance ("I hate manually renaming these files") teach far more than assigned ones, and they're what you actually talk about in interviews.
  4. Debugging by random mutation. Learn to read tracebacks bottom-up: the last line is the error type and message, and the line above it is where it happened in your code. Then use breakpoint() and inspect actual values instead of guessing.
  5. Studying alone in silence. Join a Discord, answer questions on Stack Overflow, do code review with someone. Explaining exposes gaps nothing else finds.
  6. Waiting to feel ready. Nobody feels ready. Start applying and doing mock interviews while you're still studying — the feedback redirects your effort better than any curriculum.

Part 3 — The Coding Interview Roadmap

3.1 What coding interviews actually test

A common misconception is that interviews test whether you've memorized enough problems. They don't. A typical 45-minute technical round scores you on roughly four axes:

  1. Problem solving — can you decompose an unfamiliar problem, recognize its underlying structure, and reason about tradeoffs?
  2. Coding — can you translate a plan into correct, clean code at a reasonable pace?
  3. Verification — do you test your own work, or do you announce "done" and wait to be told it's broken?
  4. Communication — can the interviewer follow your thinking? Can they collaborate with you when you're stuck?

Grinding problems only improves axis 1, and only indirectly. Candidates who have solved 600 problems and still fail are almost always failing on axes 3 and 4. Plan your preparation accordingly: solving fewer problems while thinking out loud and testing rigorously beats solving more in silence.

The other reframe that helps: the goal is pattern recognition, not problem recall. There are roughly 15–20 recurring patterns. Once you can look at a new problem and think "sorted array, looking for a pair — two pointers," you can solve problems you've never seen. That's the actual skill.

3.2 Your Python interview toolkit

Python's advantage in interviews is that the standard library hands you most data structures for free. Know these cold.

Complexity of built-in operations

This table is worth memorizing. Interviewers ask about it directly, and misjudging it silently makes your solution quadratic.

Operation Structure Average Note
arr[i] list O(1)
arr.append(x) list O(1) amortized
arr.pop() list O(1) from the end
arr.pop(0) / arr.insert(0, x) list O(n) shifts everything — use deque
x in arr list O(n) the most common accidental O(n²)
x in s set / dict O(1)
s.add(x) / d[k] = v set / dict O(1)
arr.sort() / sorted() list O(n log n) Timsort, stable
min / max / sum list O(n)
heapq.heappush/heappop heap O(log n)
heap[0] heap O(1) peek the minimum
heapq.heapify(arr) list → heap O(n) not O(n log n)
dq.appendleft/popleft deque O(1)
bisect_left/right sorted list O(log n) but inserting is O(n)
s1 & s2, s1 | s2 sets O(min/sum of sizes)
"a" + "b" in a loop str O(n²) total strings are immutable — use "".join(parts)
arr[a:b] list / str O(b − a) slicing copies

The four imports you'll use constantly

from collections import defaultdict, Counter, deque, OrderedDict
import heapq
from functools import lru_cache, cmp_to_key, reduce
import bisect
# ---------- defaultdict: no more KeyError ----------
from collections import defaultdict
graph = defaultdict(list)
graph["a"].append("b")            # key created automatically
counts = defaultdict(int)
for c in "hello":
    counts[c] += 1                # no need to initialize

# ---------- Counter: frequency in one line ----------
from collections import Counter
c = Counter("mississippi")
print(c)                          # Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
print(c.most_common(2))           # [('i', 4), ('s', 4)]
print(Counter("abc") == Counter("cba"))   # True → instant anagram check

# ---------- deque: O(1) at both ends ----------
from collections import deque
dq = deque([1, 2, 3])
dq.appendleft(0)                  # O(1)  — list.insert(0, x) is O(n)
dq.popleft()                      # O(1)
dq = deque(maxlen=3)              # auto-evicts from the other end

# ---------- heapq: min-heap only ----------
import heapq
h = []
heapq.heappush(h, 5)
heapq.heappush(h, 1)
print(h[0])                       # 1 — smallest, O(1) peek
heapq.heappop(h)                  # removes and returns 1

# max-heap trick: negate the values
maxh = []
heapq.heappush(maxh, -5)
largest = -heapq.heappop(maxh)    # 5

# top-k without sorting everything: O(n log k)
print(heapq.nlargest(3, [5, 1, 8, 3, 9]))    # [9, 8, 5]

# heap of tuples sorts by first element, then second
heapq.heappush(h, (priority, task_id, task))

# ---------- bisect: binary search on sorted lists ----------
import bisect
arr = [1, 3, 3, 5, 7]
bisect.bisect_left(arr, 3)        # 1 → first index where 3 could go
bisect.bisect_right(arr, 3)       # 3 → last index where 3 could go
bisect.insort(arr, 4)             # inserts keeping order (O(n) for the shift)

# ---------- lru_cache: memoization for free ----------
from functools import lru_cache

@lru_cache(maxsize=None)          # or @cache in Python 3.9+
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

print(fib(200))                   # instant; without the cache, effectively never finishes

Python interview gotchas that cost people offers

# 1. Integer division and negative numbers
print(7 // 2)      # 3
print(-7 // 2)     # -4  ← floors toward negative infinity, NOT -3
print(int(-7 / 2)) # -3  ← use this when you want truncation
print(-7 % 3)      # 2   ← Python's modulo returns the sign of the divisor

# 2. Recursion limit — Python's default is ~1000 frames
import sys
sys.setrecursionlimit(10**6)      # mention this if a DFS could go 10^5 deep
# Better: convert deep recursion to an iterative stack-based solution

# 3. Copying 2D grids
grid = [[0] * 3] * 3              # BROKEN — three references to ONE row
grid[0][0] = 1
print(grid)                       # [[1,0,0],[1,0,0],[1,0,0]]  ← all rows changed

grid = [[0] * 3 for _ in range(3)]   # correct

# 4. Mutating a list while iterating it
for x in nums:
    if x < 0:
        nums.remove(x)            # skips elements; silently wrong
nums = [x for x in nums if x >= 0]   # correct

# 5. Sorting by multiple keys, mixed directions
people.sort(key=lambda p: (-p.score, p.name))     # score desc, then name asc

# 6. Infinity as a sentinel
best = float("inf")
worst = float("-inf")

# 7. Custom comparators when a key function isn't enough
from functools import cmp_to_key
words.sort(key=cmp_to_key(lambda a, b: 1 if a + b < b + a else -1))

# 8. Unlimited integer precision — no overflow in Python
print(2 ** 200)   # works fine; mention this when an interviewer asks about overflow

3.3 Complexity analysis: the part you must be fluent in

You will be asked "what's the time and space complexity?" in essentially every interview. Answer without hesitation.

Growth rates, best to worst:

O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(n³) < O(2ⁿ) < O(n!)

Reverse-engineer the target complexity from the constraints. This is a genuinely powerful trick that most candidates don't use:

Constraint on n Likely intended complexity
n ≤ 10 O(n!) or O(2ⁿ) — permutations, brute-force search
n ≤ 20 O(2ⁿ) — subsets, bitmask DP
n ≤ 500 O(n³) — Floyd–Warshall, interval DP
n ≤ 5,000 O(n²) — nested loops, 2D DP
n ≤ 10⁵ O(n log n) — sorting, heaps, binary search
n ≤ 10⁶ O(n) or O(n log n) — single pass, hashing
n ≥ 10⁹ O(log n) or O(1) — binary search on the answer, math

If the problem says n ≤ 10⁵ and you're describing an O(n²) approach, the interviewer already knows you're not done. Say so yourself: "this is O(n²), which won't pass at n = 10⁵, so let me improve it."

Space complexity counts the recursion stack. A DFS on a tree of height h uses O(h) space even with no explicit data structures. Interviewers listen for whether you mention this.

3.4 Topic-by-topic roadmap

For each topic: what to learn, the patterns that appear, and representative problems. Names are given rather than links, so search them on LeetCode or NeetCode.


1. Arrays and strings — the foundation

Learn: traversal, two-pointer, in-place modification, prefix sums, sorted() with keys, string immutability.

Patterns:

Two pointers (opposite ends) — sorted arrays, palindromes, pair-finding.

def two_sum_sorted(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        total = nums[left] + nums[right]
        if total == target:
            return [left, right]
        elif total < target:
            left += 1               # need bigger
        else:
            right -= 1              # need smaller
    return []

Fast/slow pointers (same direction) — in-place removal, deduplication, cycle detection.

def remove_duplicates(nums):
    """Sorted array; returns new length, modifies in place."""
    if not nums:
        return 0
    write = 1
    for read in range(1, len(nums)):
        if nums[read] != nums[write - 1]:
            nums[write] = nums[read]
            write += 1
    return write

Prefix sums — repeated range-sum queries, subarray sums.

def subarray_sum_equals_k(nums, k):
    """Count subarrays summing to k. O(n)."""
    from collections import defaultdict
    seen = defaultdict(int)
    seen[0] = 1                     # empty prefix
    running = count = 0
    for n in nums:
        running += n
        count += seen[running - k]  # how many prefixes make up the difference
        seen[running] += 1
    return count

Problems: Two Sum · Best Time to Buy and Sell Stock · Contains Duplicate · Product of Array Except Self · Maximum Subarray (Kadane's) · Valid Palindrome · Valid Anagram · Group Anagrams · 3Sum · Container With Most Water · Merge Intervals · Rotate Array · Longest Common Prefix · Encode and Decode Strings


2. Hashing — the highest-leverage technique

Learn: dict and set as O(1) lookup, defaultdict, Counter, choosing a good key, frequency maps, seen-sets.

The core insight: almost any O(n²) "check every pair" solution becomes O(n) by trading space for time with a hash map. If you find yourself writing nested loops, ask "what could I store to avoid the inner loop?"

def two_sum(nums, target):
    """Unsorted. O(n) time, O(n) space."""
    seen = {}                        # value → index
    for i, n in enumerate(nums):
        if target - n in seen:       # have we already passed the complement?
            return [seen[target - n], i]
        seen[n] = i
    return []

def group_anagrams(words):
    """Key insight: choose a key that's identical for all anagrams."""
    from collections import defaultdict
    groups = defaultdict(list)
    for w in words:
        key = tuple(sorted(w))       # or a 26-length count tuple for O(n·k)
        groups[key].append(w)
    return list(groups.values())

Problems: Two Sum · Valid Anagram · Group Anagrams · Top K Frequent Elements · Longest Consecutive Sequence · Longest Substring Without Repeating Characters · Subarray Sum Equals K · First Missing Positive · LRU Cache


3. Sliding window

Learn: fixed-size vs variable-size windows, the expand/contract loop, when a window is valid.

Recognize it when: the problem asks about a contiguous subarray or substring that is longest/shortest/optimal subject to a condition.

Variable-size template — memorize this shape:

def longest_substring_without_repeats(s):
    window = set()
    left = best = 0
    for right, ch in enumerate(s):
        while ch in window:              # contract until valid again
            window.remove(s[left])
            left += 1
        window.add(ch)                   # expand
        best = max(best, right - left + 1)
    return best

Fixed-size template:

def max_sum_subarray_k(nums, k):
    window = sum(nums[:k])
    best = window
    for i in range(k, len(nums)):
        window += nums[i] - nums[i - k]  # slide: add new, drop old
        best = max(best, window)
    return best

Problems: Best Time to Buy and Sell Stock · Longest Substring Without Repeating Characters · Longest Repeating Character Replacement · Permutation in String · Minimum Window Substring · Sliding Window Maximum · Maximum Average Subarray


4. Stacks and queues

Learn: stack as a list, queue as a deque, monotonic stacks, matching-pair problems, expression parsing.

Monotonic stack — the pattern for "next greater/smaller element," histograms, and temperature-style problems. It looks intimidating and is actually short:

def daily_temperatures(temps):
    """For each day, days until a warmer one. O(n) — each index pushed/popped once."""
    answer = [0] * len(temps)
    stack = []                                    # indices, temps decreasing
    for i, t in enumerate(temps):
        while stack and temps[stack[-1]] < t:     # found a warmer day for these
            j = stack.pop()
            answer[j] = i - j
        stack.append(i)
    return answer
def is_valid_parentheses(s):
    pairs = {")": "(", "]": "[", "}": "{"}
    stack = []
    for ch in s:
        if ch in pairs:
            if not stack or stack.pop() != pairs[ch]:
                return False
        else:
            stack.append(ch)
    return not stack

Problems: Valid Parentheses · Min Stack · Evaluate Reverse Polish Notation · Daily Temperatures · Car Fleet · Largest Rectangle in Histogram · Generate Parentheses · Implement Queue using Stacks · Basic Calculator


5. Linked lists

Learn: node structure, dummy heads, in-place reversal, fast/slow pointers, merging.

Interviewers love linked lists precisely because they can't be brute-forced — you need pointer discipline. Two tricks solve most of them: a dummy head node, and fast/slow pointers.

class ListNode:
    def __init__(self, val=0, next=None):
        self.val, self.next = val, next

def reverse_list(head):
    prev, curr = None, head
    while curr:
        curr.next, prev, curr = prev, curr, curr.next   # the classic three-way swap
    return prev

def has_cycle(head):
    """Floyd's tortoise and hare. O(1) space."""
    slow = fast = head
    while fast and fast.next:
        slow, fast = slow.next, fast.next.next
        if slow is fast:
            return True
    return False

def merge_two_sorted(a, b):
    dummy = tail = ListNode()          # dummy head avoids special-casing the first node
    while a and b:
        if a.val <= b.val:
            tail.next, a = a, a.next
        else:
            tail.next, b = b, b.next
        tail = tail.next
    tail.next = a or b                 # attach whatever remains
    return dummy.next

Problems: Reverse Linked List · Merge Two Sorted Lists · Linked List Cycle · Reorder List · Remove Nth Node From End · Copy List with Random Pointer · Add Two Numbers · LRU Cache · Merge K Sorted Lists · Reverse Nodes in K-Group


6. Binary search

Learn: the classic form, bisect, and — most importantly — binary search on the answer.

Most candidates know basic binary search and stop there. The higher-value skill is recognizing that you can binary search over any monotonic answer space, even when there's no sorted array in sight.

def binary_search(nums, target):
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = (lo + hi) // 2              # Python ints don't overflow
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

def min_eating_speed(piles, hours):
    """Binary search on the ANSWER: what's the smallest speed that finishes in time?"""
    def hours_needed(speed):
        return sum(-(-p // speed) for p in piles)   # ceiling division

    lo, hi = 1, max(piles)
    while lo < hi:
        mid = (lo + hi) // 2
        if hours_needed(mid) <= hours:
            hi = mid              # feasible — try smaller
        else:
            lo = mid + 1          # too slow
    return lo

The signal for this pattern: "minimize the maximum," "maximize the minimum," or "find the smallest capacity/speed/size such that something is possible."

Problems: Binary Search · Search a 2D Matrix · Koko Eating Bananas · Find Minimum in Rotated Sorted Array · Search in Rotated Sorted Array · Median of Two Sorted Arrays · Split Array Largest Sum · Capacity to Ship Packages in D Days · Time-Based Key-Value Store


7. Recursion and backtracking

Learn: base case + recursive case, the decision tree, choose→explore→un-choose, pruning.

The template that covers most backtracking problems:

def subsets(nums):
    result, path = [], []

    def backtrack(start):
        result.append(path[:])              # copy — path keeps mutating
        for i in range(start, len(nums)):
            path.append(nums[i])            # choose
            backtrack(i + 1)                # explore
            path.pop()                      # un-choose

    backtrack(0)
    return result

def permutations(nums):
    result = []

    def backtrack(path, remaining):
        if not remaining:
            result.append(path[:])
            return
        for i in range(len(remaining)):
            backtrack(path + [remaining[i]], remaining[:i] + remaining[i+1:])

    backtrack([], nums)
    return result

Three questions to ask yourself for any backtracking problem: What's a partial solution? What choices extend it? When do I stop? Answer those and the code writes itself.

Complexity here is usually exponential and that's expected: subsets are O(n·2ⁿ), permutations O(n·n!). Say the number out loud; interviewers want to know you understand the cost.

Problems: Subsets · Subsets II · Combination Sum · Permutations · Word Search · Palindrome Partitioning · Letter Combinations of a Phone Number · N-Queens · Sudoku Solver · Generate Parentheses


8. Trees

Learn: binary trees, BSTs, all four traversals, recursive vs iterative, BFS by level, tree DP.

Trees are the highest-frequency topic in real interviews after arrays and hashing. The good news: 80% of tree problems are one of three templates.

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val, self.left, self.right = val, left, right

# Template 1: recursive DFS — "solve for children, combine"
def max_depth(root):
    if not root:
        return 0
    return 1 + max(max_depth(root.left), max_depth(root.right))

def is_balanced(root):
    def height(node):
        if not node:
            return 0
        lh, rh = height(node.left), height(node.right)
        if lh == -1 or rh == -1 or abs(lh - rh) > 1:
            return -1                   # sentinel signals "unbalanced" upward
        return 1 + max(lh, rh)
    return height(root) != -1

# Template 2: BFS by level — anything about levels, widths, or shortest paths
from collections import deque

def level_order(root):
    if not root:
        return []
    result, queue = [], deque([root])
    while queue:
        level = []
        for _ in range(len(queue)):     # snapshot the size: this is one level
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        result.append(level)
    return result

# Template 3: BST property — inorder is sorted, and comparisons prune half the tree
def validate_bst(root):
    def check(node, low, high):
        if not node:
            return True
        if not (low < node.val < high):
            return False
        return check(node.left, low, node.val) and check(node.right, node.val, high)
    return check(root, float("-inf"), float("inf"))

Problems: Invert Binary Tree · Maximum Depth · Same Tree · Subtree of Another Tree · Lowest Common Ancestor of a BST · Level Order Traversal · Validate BST · Kth Smallest Element in a BST · Construct Tree from Preorder and Inorder · Binary Tree Maximum Path Sum · Serialize and Deserialize Binary Tree · Diameter of Binary Tree


9. Heaps and priority queues

Learn: heapq, min vs max heaps, top-k, two-heap median, merging sorted streams.

Recognize it when: you need repeated access to the smallest or largest element, or "top k" / "kth largest" / "median of a stream."

def k_closest_to_origin(points, k):
    """O(n log k) with a size-k max-heap, better than O(n log n) sorting."""
    import heapq
    heap = []
    for x, y in points:
        heapq.heappush(heap, (-(x*x + y*y), x, y))   # negate → max-heap
        if len(heap) > k:
            heapq.heappop(heap)                       # evict the farthest
    return [[x, y] for _, x, y in heap]

class MedianFinder:
    """Two heaps: max-heap for the lower half, min-heap for the upper half."""
    def __init__(self):
        self.low, self.high = [], []      # low is negated

    def add(self, num):
        import heapq
        heapq.heappush(self.low, -num)
        heapq.heappush(self.high, -heapq.heappop(self.low))   # balance
        if len(self.high) > len(self.low):
            heapq.heappush(self.low, -heapq.heappop(self.high))

    def median(self):
        if len(self.low) > len(self.high):
            return -self.low[0]
        return (-self.low[0] + self.high[0]) / 2

Problems: Kth Largest Element in an Array · Top K Frequent Elements · Find Median from Data Stream · Merge K Sorted Lists · Task Scheduler · K Closest Points to Origin · Last Stone Weight · Design Twitter


10. Graphs

Learn: adjacency list representation, BFS, DFS, cycle detection, topological sort, union-find, Dijkstra.

Represent graphs as defaultdict(list) unless told otherwise. And remember: a 2D grid is a graph, where neighbors are the four adjacent cells. Recognizing that turns half of all grid problems into standard BFS/DFS.

from collections import defaultdict, deque

# ---------- BFS: shortest path in an unweighted graph ----------
def bfs_shortest(graph, start, goal):
    queue = deque([(start, 0)])
    visited = {start}
    while queue:
        node, dist = queue.popleft()
        if node == goal:
            return dist
        for nxt in graph[node]:
            if nxt not in visited:
                visited.add(nxt)         # mark on ENQUEUE, not dequeue
                queue.append((nxt, dist + 1))
    return -1

# ---------- Grid DFS: connected components ----------
def count_islands(grid):
    if not grid:
        return 0
    rows, cols = len(grid), len(grid[0])

    def sink(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] != "1":
            return
        grid[r][c] = "0"                 # mark visited in place
        for dr, dc in ((1,0), (-1,0), (0,1), (0,-1)):
            sink(r + dr, c + dc)

    islands = 0
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == "1":
                islands += 1
                sink(r, c)
    return islands

# ---------- Topological sort (Kahn's): dependency ordering ----------
def topo_sort(n, prerequisites):
    graph = defaultdict(list)
    indegree = [0] * n
    for course, prereq in prerequisites:
        graph[prereq].append(course)
        indegree[course] += 1

    queue = deque(i for i in range(n) if indegree[i] == 0)
    order = []
    while queue:
        node = queue.popleft()
        order.append(node)
        for nxt in graph[node]:
            indegree[nxt] -= 1
            if indegree[nxt] == 0:
                queue.append(nxt)
    return order if len(order) == n else []      # short result ⇒ there's a cycle

# ---------- Union-Find: connectivity, cycle detection, Kruskal's MST ----------
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [1] * n
        self.components = n

    def find(self, x):
        while self.parent[x] != x:
            self.parent[x] = self.parent[self.parent[x]]   # path compression
            x = self.parent[x]
        return x

    def union(self, a, b):
        ra, rb = self.find(a), self.find(b)
        if ra == rb:
            return False                          # already connected → cycle
        if self.rank[ra] < self.rank[rb]:
            ra, rb = rb, ra
        self.parent[rb] = ra
        self.rank[ra] += self.rank[rb]
        self.components -= 1
        return True

# ---------- Dijkstra: shortest path with weights ----------
def dijkstra(graph, start):
    import heapq
    dist = {start: 0}
    heap = [(0, start)]
    while heap:
        d, node = heapq.heappop(heap)
        if d > dist.get(node, float("inf")):
            continue                              # stale entry
        for nxt, weight in graph[node]:
            nd = d + weight
            if nd < dist.get(nxt, float("inf")):
                dist[nxt] = nd
                heapq.heappush(heap, (nd, nxt))
    return dist

Problems: Number of Islands · Clone Graph · Max Area of Island · Pacific Atlantic Water Flow · Surrounded Regions · Rotting Oranges · Course Schedule · Course Schedule II · Redundant Connection · Number of Connected Components · Graph Valid Tree · Word Ladder · Network Delay Time · Cheapest Flights Within K Stops · Alien Dictionary


11. Dynamic programming

Learn: memoization vs tabulation, state definition, transitions, the classic families.

DP is where most candidates struggle, and the reason is almost always that they try to memorize solutions instead of learning the process. The process:

  1. Can this be solved by making a sequence of choices, where subproblems repeat? If yes, DP is on the table.
  2. Define the state. What minimal information identifies a subproblem? This is the hard step. Write it as an English sentence first: "dp[i] = the length of the longest increasing subsequence ending at index i."
  3. Write the recurrence. How does the answer at this state depend on smaller states?
  4. Identify base cases.
  5. Write it top-down with @cache first — it's closer to the recursive intuition and harder to get wrong.
  6. Convert to bottom-up if asked, then optimize space if only the last row or two is needed.
from functools import cache

# Step 5 in practice: brute-force recursion + one decorator = DP
@cache
def climb(n):
    if n <= 2:
        return n
    return climb(n - 1) + climb(n - 2)

# Bottom-up with O(1) space
def climb_iterative(n):
    a, b = 1, 2
    for _ in range(n - 2):
        a, b = b, a + b
    return b if n > 1 else 1

# ---------- 0/1 knapsack: the archetype ----------
def knapsack(weights, values, capacity):
    dp = [0] * (capacity + 1)              # dp[c] = best value with capacity c
    for w, v in zip(weights, values):
        for c in range(capacity, w - 1, -1):   # iterate DOWN so each item is used once
            dp[c] = max(dp[c], dp[c - w] + v)
    return dp[capacity]

# ---------- Longest common subsequence: the 2D archetype ----------
def lcs(a, b):
    dp = [[0] * (len(b) + 1) for _ in range(len(a) + 1)]
    for i in range(1, len(a) + 1):
        for j in range(1, len(b) + 1):
            if a[i-1] == b[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    return dp[-1][-1]

# ---------- Longest increasing subsequence ----------
def lis(nums):
    """O(n log n) using patience sorting + bisect."""
    import bisect
    tails = []
    for n in nums:
        i = bisect.bisect_left(tails, n)
        if i == len(tails):
            tails.append(n)
        else:
            tails[i] = n
    return len(tails)

The DP families to recognize:

Family State shape Examples
Linear / Fibonacci-like dp[i] Climbing Stairs, House Robber, Decode Ways
Knapsack (0/1 and unbounded) dp[i][capacity] Partition Equal Subset Sum, Coin Change, Target Sum
Two sequences dp[i][j] LCS, Edit Distance, Regex Matching
Grid dp[r][c] Unique Paths, Minimum Path Sum
Intervals dp[i][j] over ranges Burst Balloons, Matrix Chain, Palindrome Partitioning II
Subsequence dp[i] + inner loop LIS, Longest Palindromic Subsequence
Bitmask dp[mask] Travelling Salesman, Partition to K Subsets
State machine dp[i][state] Best Time to Buy/Sell Stock with Cooldown / Fee / K Transactions

Problems (in this order): Climbing Stairs · Min Cost Climbing Stairs · House Robber · House Robber II · Coin Change · Longest Palindromic Substring · Palindromic Substrings · Decode Ways · Maximum Product Subarray · Word Break · Longest Increasing Subsequence · Partition Equal Subset Sum · Unique Paths · Longest Common Subsequence · Edit Distance · Best Time to Buy and Sell Stock with Cooldown · Coin Change II · Target Sum · Interleaving String · Burst Balloons · Regular Expression Matching


12. Greedy and intervals

Learn: exchange arguments, sorting as a preprocessing step, interval merging and scheduling.

Greedy is the pattern where a locally optimal choice yields a global optimum. The risk is that greedy feels right and is wrong. If you propose a greedy solution, be prepared to justify why the local choice is safe — or to acknowledge you'd want to prove it. Interviewers respect "I think greedy works here because sorting by end time means we never block a better option, though I'd want to verify with a counterexample search" far more than unearned confidence.

def merge_intervals(intervals):
    intervals.sort(key=lambda x: x[0])          # sort by start
    merged = []
    for start, end in intervals:
        if merged and start <= merged[-1][1]:   # overlaps the previous
            merged[-1][1] = max(merged[-1][1], end)
        else:
            merged.append([start, end])
    return merged

def max_non_overlapping(intervals):
    """Classic activity selection: sort by END time, greedily take earliest finisher."""
    intervals.sort(key=lambda x: x[1])
    count, last_end = 0, float("-inf")
    for start, end in intervals:
        if start >= last_end:
            count += 1
            last_end = end
    return count

def can_jump(nums):
    """Track the farthest reachable index."""
    reach = 0
    for i, n in enumerate(nums):
        if i > reach:
            return False
        reach = max(reach, i + n)
    return True

Problems: Maximum Subarray · Jump Game · Jump Game II · Gas Station · Merge Intervals · Insert Interval · Non-overlapping Intervals · Meeting Rooms · Meeting Rooms II · Minimum Number of Arrows · Partition Labels · Task Scheduler · Hand of Straights


13. Tries, bit manipulation, and math

Lower frequency, but they show up — and they're small enough to learn quickly.

Trie — prefix-based problems, autocomplete, word search on grids:

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_word = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        node = self.root
        for ch in word:
            node = node.children.setdefault(ch, TrieNode())
        node.is_word = True

    def search(self, word, prefix_only=False):
        node = self.root
        for ch in word:
            if ch not in node.children:
                return False
            node = node.children[ch]
        return True if prefix_only else node.is_word

Bit manipulation — the operations worth knowing:

x & 1                      # is odd?
x >> 1                     # divide by 2
x << 1                     # multiply by 2
x ^ y                      # XOR: a ^ a == 0, a ^ 0 == a → finds the unique element
x & (x - 1)                # clears the lowest set bit
x & -x                     # isolates the lowest set bit
bin(x).count("1")          # popcount
x.bit_count()              # popcount, Python 3.10+
1 << i                     # the i-th bit as a mask
mask & (1 << i)            # test bit i
mask | (1 << i)            # set bit i
mask ^ (1 << i)            # flip bit i

def single_number(nums):
    """Every element appears twice except one. O(n) time, O(1) space."""
    from functools import reduce
    import operator
    return reduce(operator.xor, nums)

Math — GCD/LCM, primes via the Sieve of Eratosthenes, modular arithmetic (pow(base, exp, mod)), fast exponentiation, and combinatorics (math.comb).

Problems: Implement Trie · Design Add and Search Words · Word Search II · Single Number · Number of 1 Bits · Counting Bits · Reverse Bits · Missing Number · Sum of Two Integers · Pow(x, n) · Happy Number · Plus One · Count Primes

3.5 The 14-week interview study plan

Assumes ~12–15 hours per week alongside a job. If you have more time, compress; don't skip.

Week Focus Target problems Milestone
1 Complexity analysis, arrays, two pointers 12 easy Can state complexity for any code you write
2 Hashing, strings 12 easy / 3 medium Two Sum family solved instantly
3 Sliding window 8 medium Can derive the template from scratch
4 Stacks, queues, monotonic stack 8 medium Next-greater-element without hints
5 Linked lists 10 medium Reverse and merge from memory
6 Binary search + on the answer 8 medium Recognize "minimize the maximum"
7 Review week + first mock interview Redo 15 previously failed problems Honest gap list written down
8 Recursion, backtracking 8 medium Subsets/permutations template fluent
9 Trees part 1: DFS, BFS 12 medium All three tree templates from memory
10 Trees part 2: BST, tree DP, tries 8 medium / 2 hard Serialize/deserialize solved
11 Heaps, greedy, intervals 10 medium Top-k without sorting
12 Graphs: BFS/DFS/topo/union-find 12 medium / 2 hard Grid problems feel routine
13 Dynamic programming 12 medium / 3 hard Can define state for a new problem
14 Mocks, weak spots, behavioral prep 5 mixed daily, timed 3+ mock interviews completed

Target totals: roughly 150–200 problems solved with genuine understanding. The distribution that matters: about 25% easy, 60% medium, 15% hard. Medium problems are where interviews actually live — most companies ask two mediums, not one hard.

The number people quote (300, 500, "all of LeetCode") is mostly signaling. 150 problems you can re-solve from scratch and explain beat 500 you pattern-matched once. Which brings us to the most important habit in this entire guide:

Keep a mistake log. One row per problem you failed or needed a hint on:

Problem Pattern Why I missed it Re-solve date
Coin Change DP, unbounded knapsack Defined state as "coins used" instead of "amount remaining" +3 days, then +2 weeks

Then actually re-solve them on those dates, from a blank file. This log is worth more than the next 100 new problems, because it targets your specific weaknesses rather than reinforcing what you already know. Almost nobody does this. It's the highest-return habit available to you.

3.6 How to practice each problem

A repeatable loop, roughly 35–50 minutes per problem:

  1. Read the problem and constraints. Use the constraints to guess the target complexity (see the table in 3.3).
  2. Set a timer for 20–25 minutes and think without coding. Write your approach in plain English or pseudocode. If you're stuck at 25 minutes, look at only a hint, not the solution.
  3. Code it. No autocomplete of full solutions, no AI. Type it.
  4. Test on paper before running. Trace one normal case and one edge case by hand. This builds the verification habit interviewers grade you on.
  5. Submit. If it fails, debug it yourself before reading anything.
  6. After solving, read the top solutions anyway. There is almost always a cleaner approach, and cleanliness is graded.
  7. State the complexity out loud. Time and space.
  8. Log it if you struggled.
  9. Re-solve from blank after 3 days and again after 2 weeks if it was hard.

Step 9 is the one everyone skips and the one that produces retention. Solving a problem once creates recognition; solving it again from scratch after a gap creates recall. Interviews test recall.


Part 4 — Executing in the Interview

This is the part with the highest return per hour invested, and the part almost everyone under-prepares. Two candidates with identical algorithmic ability routinely get different outcomes because one of them managed the 45 minutes well.

4.1 The framework: five phases, timed

For a 45-minute coding round:

Phase Time What you do
1. Clarify 3–5 min Restate the problem, ask about constraints and edge cases
2. Examples 2–3 min Walk one concrete example; confirm your understanding matches theirs
3. Approach 5–8 min Propose brute force, then optimize, state complexity, get buy-in before coding
4. Code 15–20 min Write it while narrating
5. Test & refine 5–8 min Trace a case, handle edges, discuss improvements

The most common fatal error is skipping straight to phase 4. Coding a solution the interviewer hasn't agreed to means that if you've misread the problem, you burn 20 minutes before finding out. Getting buy-in costs 30 seconds.

Phase 1 — Clarify. What to ask.

Have these ready as a mental checklist so you're never silent at the start:

  • "Let me make sure I have this right: given ___, I need to return ___. Is that correct?"
  • "What's the expected size of the input?" (determines target complexity)
  • "Can the input be empty or null?"
  • "Are the values sorted? Can there be duplicates? Negatives?"
  • "For strings — ASCII or Unicode? Case-sensitive?"
  • "If there's no valid answer, what should I return?"
  • "If there are multiple valid answers, is any one acceptable?"
  • "Can I modify the input in place, or should I preserve it?"
  • "Should I optimize for time or space if there's a tradeoff?"

Ask three or four of the relevant ones, not all nine. Reading the problem statement carefully answers some of them already, and asking about things the prompt clearly states reads as inattention.

Phase 3 — Approach. The script that works.

Say something structurally like this:

"The brute-force approach is to check every pair, which is O(n²) time and O(1) space. Given the constraint that n can be up to 10⁵, that's too slow. The bottleneck is re-scanning to find the complement. If I store what I've seen in a hash map, I can look up the complement in O(1), which gets me to O(n) time and O(n) space. Does that sound reasonable to start coding?"

That paragraph does five things at once: shows you can find a solution, shows you know it's insufficient and why, identifies the specific bottleneck, states the improvement with complexity, and invites collaboration. It is the single highest-value 30 seconds of the interview.

Always mention brute force first, even briefly, even if you immediately see the optimal solution. It demonstrates that your optimal solution is reasoned rather than memorized — which is exactly the doubt interviewers hold about candidates who've ground problems.

Phase 4 — Code. How to narrate.

Narrate intent, not syntax. Nobody wants "now I'm typing a for loop."

  • Good: "I'll use a set to track the current window characters, so membership checks are O(1)."
  • Bad: "So, for, i, in, range, len, s..."

Other habits that read as senior:

  • Write clean names. left/right, seen, count, result — not a, b, x2, temp2.
  • Handle the guard clauses first. if not nums: return [] at the top signals edge-case awareness immediately.
  • If you need a helper, stub it and move on. "I'll assume a is_valid(board) helper exists and implement it after the main logic." This keeps momentum and shows decomposition.
  • Say when you're unsure of an API. "I think bisect_left returns the leftmost insertion point — I'd verify that in a real environment, but the logic holds either way." This is honesty, and it's scored positively. Confidently wrong is scored negatively.

Phase 5 — Test. What separates strong candidates.

Do not say "I'm done." Say "let me trace through this." Then actually do it — pick a small input and walk your variables line by line, out loud. When you find a bug this way, you've just demonstrated the single most valuable engineering trait there is.

Edge cases to check by reflex:

  • empty input, single element, two elements
  • all elements identical
  • already-sorted and reverse-sorted input
  • negatives and zero
  • the maximum constraint (does it overflow, recurse too deep, or time out?)
  • for trees: single node, one-sided/skewed tree
  • for graphs: disconnected components, self-loops, cycles

Then close with a forward-looking sentence: "This is O(n) time and O(n) space. If memory were constrained, I could sort first and use two pointers for O(n log n) time and O(1) space instead."

4.2 What to do when you're stuck

You will get stuck. It's expected. How you get unstuck is itself the thing being evaluated. The failure mode is silence — an interviewer watching a candidate stare at a blank screen for four minutes has nothing to score.

An escalating sequence that works:

  1. Verbalize where you are. "I know I need to track the count of each character. What I'm not seeing yet is how to know when to shrink the window." Now the interviewer knows your exact blocker and can nudge you — that's what they're there for.
  2. Try a smaller case. Solve n=2 and n=3 by hand. Patterns emerge from concrete cases far more reliably than from staring at the abstraction.
  3. Run down your pattern list. Out loud: "Is this a sliding window? There's no contiguity requirement, so probably not. Could I sort first? That would let me use two pointers..."
  4. Ask what's redundant. Most optimizations come from eliminating repeated work. What am I computing more than once? Could I cache it, precompute it, or maintain it incrementally?
  5. Write the brute force. A working O(n²) solution scores far better than an unwritten O(n) one. Then optimize from working code.
  6. Take the hint gracefully. If offered one, say "that's helpful, thank you" and use it. Resisting help reads as rigid. Taking a hint costs you a little; refusing one and failing costs you the round.

4.3 Mock interviews are non-negotiable

Solving problems alone in your room prepares you for solving problems alone in your room. It does not prepare you for coding while a stranger watches, in a plain editor with no autocomplete, while explaining yourself.

Do at least five mock interviews before your first real one. Options: Pramp and interviewing.io (free peer mocks), a friend in the industry, a paid coach, or — the accessible version — record yourself solving a random problem out loud on a 45-minute timer and watch it back. Watching yourself is uncomfortable and unusually instructive; you'll notice the long silences, the filler, and the moments you started coding without a plan.

Practice in an environment like the real one: a plain text editor or a shared doc, no IDE, no autocomplete, no running the code. Write and reason without a safety net, because that's the actual condition.

4.4 The other rounds

Coding isn't the whole loop. A rough sense of the full picture:

Round Typical presence What to prepare
Screen (recruiter) Always A crisp 90-second background summary; salary expectations; why this company
Online assessment Common at large firms Timed 2–4 problems; practice under time pressure specifically
Coding rounds 1–4 rounds Everything in Part 3 and this section
Python/language deep-dive Common for Python-specific roles See 4.5 below
System design Mid-level and above Scaling, databases, caching, queues, load balancing, tradeoffs
Behavioral Always 6–8 STAR stories
Hiring manager Usually Your motivations, questions about the team, project ownership

Behavioral prep, briefly: prepare six to eight stories in STAR form (Situation, Task, Action, Result) covering: a conflict with a coworker, a failure and what you learned, a project you led, a time you received hard feedback, a time you had to learn something fast, a time you disagreed with a decision, and something you're proud of. Have real numbers in the Results where possible. Rehearse them out loud — they should take about two minutes each. The same six stories, recombined, answer nearly every behavioral question asked.

4.5 Python-specific questions you should expect

If you list Python as a strength, expect to be tested on the language and not just algorithms. Be able to answer these clearly:

  • List vs tuple vs set vs dict — mutability, ordering, use cases, and which can be dict keys.
  • is vs == — identity versus equality. And why [] == [] is True but [] is [] is False.
  • Shallow vs deep copycopy.copy vs copy.deepcopy, and when nested structures bite you.
  • Mutable default arguments — why def f(x=[]) is a bug.
  • How dicts achieve O(1) — hashing, collision handling, why keys must be immutable, and that dicts have preserved insertion order since 3.7.
  • Generators vs lists — laziness, memory, when you can't reuse a generator.
  • Decorators — what @something desugars to, and why functools.wraps exists.
  • *args and **kwargs — and argument unpacking at the call site.
  • The GIL — what it is, why threads help I/O-bound but not CPU-bound work, threading vs multiprocessing vs asyncio.
  • Context managers — what with guarantees, and how to write one with __enter__/__exit__ or @contextmanager.
  • __init__ vs __new__, and what self actually is.
  • Method resolution order — how Python resolves attributes across multiple inheritance.
  • Duck typing and EAFP — "easier to ask forgiveness than permission": try the operation and catch the exception, rather than checking types first.
  • Iterators vs iterables__iter__ and __next__.
  • @staticmethod vs @classmethod — and when each is appropriate.
  • List comprehension vs map/filter — readability and performance.
  • async/await — the event loop, and when concurrency helps.

If you can discuss the GIL, generators, and mutability with genuine understanding, you will sound more like an engineer than most candidates who've solved twice as many problems.

4.6 Résumé, projects, and GitHub

Interviews start before the interview.

Résumé: one page. Every bullet in the form action verb + what you did + measurable outcome. "Built an ETL pipeline in Python processing 2M records daily, cutting report generation from 6 hours to 12 minutes" beats "Responsible for data pipelines" by an enormous margin. List the specific tools; recruiters and automated systems both filter on keywords.

Projects: three good ones beat ten tutorial clones. A strong project has a real user need (even if the user is you), a README that shows what it does and how to run it, tests, clean commit history, and — ideally — deployment so it can actually be clicked. Avoid the tutorial trilogy of to-do app, weather app, and calculator unless you've taken them somewhere unusual.

GitHub: pin your best three. Make sure every pinned repo has a real README with a screenshot or example output. A repo with no README reads as abandoned.

Be able to defend everything you list. If your résumé says Docker, expect "walk me through your Dockerfile." If a project used a library, expect "why that one and not the alternative?" One unanswerable claim casts doubt on the entire document.

4.7 The final week, the day before, the day of

Final week: stop learning new topics. Review your mistake log, re-solve 20 problems you previously failed, rehearse your STAR stories out loud, and do two mocks. New material this late adds anxiety, not capability.

Day before: one light review session, maximum two hours. Prepare your setup — test your camera, microphone, and internet; have water and paper nearby; know the editor. Prepare three questions to ask the interviewer about the team and the work. Then stop and sleep properly. Sleep affects working memory more than one additional day of study affects your knowledge, and working memory is the resource an interview consumes.

Day of: eat something. Arrive or log in five minutes early. Have paper for diagramming. Then, during the interview: slow down. Nearly every candidate rushes. Reading the problem twice costs 20 seconds and prevents the most expensive mistake available to you.

Afterward: write down every question you were asked and every place you struggled, within an hour, while it's fresh. Whether you get the offer or not, that record is the most accurate study guide you will ever have for the next loop.

4.8 On rejection

You will be rejected, probably several times, possibly for reasons unrelated to your ability — headcount freezes, an internal candidate, a slightly stronger applicant, or an interviewer having a bad day. The process is noisy. Strong engineers get rejected by companies that later hire weaker ones.

Two things to do with it: ask for feedback (you'll rarely get specifics, but occasionally you'll get something valuable), and log the questions that beat you. Then apply again. Most companies allow reapplication after 6–12 months, and candidates who return with visible improvement are viewed favorably.

Don't treat one interview as a verdict on your competence. It's a sample from a distribution, and you control the distribution by continuing to practice and by taking more samples.


Appendix A — Quick reference: pattern → signal

Print this. It's the fastest way to build the recognition reflex.

If the problem says... Reach for
Sorted array, find a pair/triple Two pointers
Contiguous subarray/substring with a condition Sliding window
"Find duplicates," "have I seen this," count frequency Hash map / set / Counter
Top k, kth largest, median of a stream Heap
Repeated range sums Prefix sum
Next greater/smaller element, histogram Monotonic stack
Matching pairs, nesting, undo, expression parsing Stack
"Shortest path" in an unweighted graph or grid BFS
Explore all paths, connected components, flood fill DFS
Shortest path with weights Dijkstra
Dependencies, ordering, "can this be completed" Topological sort
Connectivity, "are these in the same group," cycle in undirected Union-Find
Sorted input or monotonic answer space Binary search
"Minimize the maximum" / "maximize the minimum" Binary search on the answer
All combinations/permutations/subsets, constraint satisfaction Backtracking
Count ways, min/max over sequential choices, overlapping subproblems Dynamic programming
Prefix matching, autocomplete, dictionary of words Trie
Overlapping ranges, scheduling Sort by start or end + greedy
Find the unique element, "O(1) space" with pairs XOR / bit manipulation
Linked list, "without extra space" Fast/slow pointers, dummy head
Level-by-level, tree width, minimum depth BFS with a level snapshot
In-order gives sorted, search prunes half BST properties
n ≤ 20, subsets of a set as state Bitmask DP

Appendix B — The consolidated resource list

Learning Python: the official docs tutorial · Automate the Boring Stuff (free) · CS50P (free) · Real Python · Fluent Python (intermediate+) · Python Tricks (Dan Bader)

Practice: Exercism (mentored) · Advent of Code · Codewars · HackerRank

Interview prep: NeetCode 150 / NeetCode roadmap (the best-curated free problem list) · LeetCode · Blind 75 · Grokking the Coding Interview (pattern-based) · Cracking the Coding Interview (dated on specifics, still good on process) · Elements of Programming Interviews in Python · Tech Interview Handbook (free, excellent on the non-coding parts)

Mocks: Pramp · interviewing.io · a friend · a recording of yourself

Systems and design: Designing Data-Intensive Applications · System Design Primer (free on GitHub) · High Scalability

Staying current: Real Python and Talk Python podcasts · the Python Discord · r/learnpython for questions, r/Python for news


Appendix C — Frequently asked questions

How long until I can get a job? With no programming background and consistent effort: 8–14 months to interview-ready. With an existing programming background: 3–5 months. With a CS degree needing interview prep only: 2–3 months. These are honest medians, not floors — some people take longer and still succeed.

Is Python a good choice for coding interviews? Yes, and it's arguably the best. Less code means less time and fewer bugs under pressure, and the standard library gives you heaps, deques, counters, and caching for free. The only caveats: a handful of companies specify a language (usually for infrastructure roles), and Python's slowness can occasionally cause a timeout on a solution that would pass in C++ — rare, and usually a sign your complexity is wrong rather than your language.

How many problems do I need to solve? 150–200 solved with real understanding, weighted toward mediums. Quality dominates quantity past that point. If you can't re-solve a problem from blank three weeks later, it doesn't count toward your total.

Should I use AI assistants while learning? Yes, for explanation, critique, and quizzing. No, for writing code you'll claim as your own or for skipping the struggle. The struggle is not an obstacle to learning; it is the learning. And you'll be asked to explain, in real time, code you can't have generated in the room.

Do I need a CS degree? No, but you need to know what a CS degree teaches: data structures, algorithms, complexity, some operating systems and networking basics, and databases. Self-taught engineers get hired constantly. They just can't skip the fundamentals and expect to pass a technical loop.

Is LeetCode grinding actually useful, or is it just hazing? Partly both. It's an imperfect proxy that over-weights pattern recall. But it does build real skills — decomposition, complexity reasoning, careful implementation — and it's the filter that exists, so treating it as a game to be understood rather than an injustice to be resented is the more useful stance.

Should I learn another language too? Not until Python is solid and you have a job or offers. Then yes — a second language (Go, Rust, TypeScript, or C++, depending on your track) deepens your understanding of the first by showing you which of Python's behaviors are universal and which are choices.

What if I forget everything I've learned? You won't forget the important parts if you built things and kept a mistake log. And you don't need to hold it all in memory — you need to hold the patterns and know where to look up the rest. That's what professional programming actually is.


The short version

If you take four things from this guide:

  1. Build things. Reading and watching create the illusion of competence. Only writing code that fails and then works creates the real thing.
  2. Learn patterns, not problems. Fifteen patterns generalize; four hundred memorized solutions don't.
  3. Keep a mistake log and actually re-solve from it. This is the highest-return habit in the whole document and almost nobody does it.
  4. Practice talking while you code. Interviews score communication and verification, not just correctness — and this is where equally capable candidates diverge.

Start today, with something small. Install Python, create a virtual environment, and write a program that does one thing you find slightly annoying. That's the whole beginning.