LearnThatStack Ace your next interview

Python

Python 3.10+ on CPython. Newer rows name their version. Checked Nov 2026 138 code snippets

The model

  • A name is a label bound to an object. Assignment and function calls copy nothing, so two names can share one object.
  • Mutable objects change in place. Immutable objects can't, so any change builds a new object.
  • CPython compiles source to bytecode, and its virtual machine interprets it. Reference counting frees most objects.
  • The GIL lets one thread run Python bytecode at a time. Threads suit I/O-bound work, and processes suit CPU-bound work.
ab[1, 2]
assignb = a gives two names for one list.
ab[1, 2, 3]
mutateb.append(3) changes the list, so a sees it too.
ab[1, 2, 3][9]
rebindb = [9] moves only b. The name a keeps its list.

Names & objects

what assignment, copy and == do

Compare with == or is

Compare valuesa == b
Compare identitya is b

Tip== calls __eq__, so each class decides what equal means. Use is only for singletons, as in value is None.

TrapCPython reuses small integers, so x is y can be true by accident. Never use is to compare numbers or strings.

Mutability and passing arguments

Name the mutable typeslist, dict, set and most objects of your own classes.
Name the immutable typesNumbers, str, tuple, frozenset and bytes.
Change the caller's listitems.append(4)
Rebind a parameteritems = [0]Only the local name moves.

TipPython passes by assignment, with no copy. Immutable arguments can only be rebound, so they behave as if passed by value.

TrapA function that changes a mutable argument also changes the caller's object. To protect it, pass items.copy().

Shallow or deep copy

originalcopyinner
shallowA new outer list that shares the inner list.
originalcopyinnerinner
deepThe inner list gets copied too, so nothing is shared.
Make a shallow copycopy.copy(a)a.copy()a[:]
Make a deep copycopy.deepcopy(a)

Tipdeepcopy is slower and uses more memory. It also handles objects that refer to themselves.

TrapAfter a shallow copy, changing shallow[0][0] changes the original too. The difference shows only with nested, mutable objects.

Local and global scope

Look up a nameLEGB. Local, enclosing, global, then built-in.
Rebind an outer nameglobal nnonlocal n

TrapAssign to a name anywhere in a function and it's local for the whole body. Reading it first raises UnboundLocalError.

Types & collections

pick the right container

Built-in types

Name the built-in typesint, float, complex, str, list, tuple, set, frozenset, dict, bool, NoneType.

TipPython is dynamically typed. A variable has no fixed type, and type(value) tells you the type of the value it holds.

List or tuple

Hold items that change[1, 2, 3]
Hold a fixed group(1, 2)

TipA tuple is a little faster and smaller, because Python allocates it once at a fixed size.

TrapA list can't be a dict key or a set member. A tuple can, when all its items are hashable.

Dictionaries

Read a required keyd[key]
Read an optional keyd.get(key, default)

TipA lookup takes O(1) on average. d[key] raises KeyError on a missing key. Since Python 3.7, a dict keeps insertion order.

TrapKeys must be hashable. Strings, numbers and tuples of hashable items work, but lists don't.

Comprehensions

Build a filtered list[x*x for x in a if x]
Build a set, a dict or a generator{x for x in a}{k: v for k, v in a}(x for x in a)

TrapParentheses build a generator expression, not a tuple.

Built-in functions to know

Sort a copy, or in placesorted(a)a.sort()
Map, filter or pair itemsmap(f, a)filter(f, a)zip(a, b)
Inspect an objectisinstance(x, int)hasattr(x, "y")

Trapmap, filter, enumerate and zip return lazy iterators, not lists. Wrap one in list() to see its values.

Syntax, style & loops

the shape of a Python file

Indentation, PEP 8 and names

Mark a code blockIndent it. PEP 8 says 4 spaces per level.
Name functions, classes and constantssnake_caseCapWordsUPPER_CASE
Write an identifierUse a letter or _ first, then letters, digits and _. Case matters, and it can't be a keyword.

TipPEP 8 also limits lines to 79 characters. It orders imports as standard library, third-party, then your own modules.

TrapA wrong indent moves a line into another block or stops the file from running. Python raises IndentationError, or TabError for mixed tabs and spaces.

Conditions and loops

Run the first true blockif a:elif b:else:
Pick one of two valuesx if ok else y
Match patterns (3.10+)match value:
Loop over items, or while a condition holdsfor x in a:while n < 3:
Loop with the indexenumerate(a)
End a loop, skip an iteration, or do nothingbreakcontinuepass

TipA condition doesn't need to be a Boolean. Python treats 0, empty collections and None as false.

Trapbreak exits only the innermost loop. It also skips the loop's else clause, which runs only when no break happened.

Python 2 or Python 3

Print in Python 2print "hello"Python 3 uses print("hello").
Divide in Python 25 / 2It gave 2. Python 3 gives 2.5, and // does floor division.
Read input in Python 2raw_input()Python 3 uses input(), which returns a string.
Count lazily in Python 2xrange(n)Python 3's range() works like it.

TipPython 2 reached end of life on January 1, 2020. Python 3 strings are Unicode by default, with a separate bytes type.

TrapA range is a lazy sequence, not an iterator. It supports len(), indexing and in, and you can loop over it many times.

Functions & decorators

arguments, closures, wrappers

Closures and lambdas

def make_multiplier(factor): def multiply(x): return x * factorremembers factor return multiply
Capture a loop value nowlambda n=x: n
Sort by one fieldkey=lambda s: s[1]

TipA lambda is one expression, with no loops or assignments. A def gets a real name in tracebacks.

TrapClosures use late binding, so they read a variable when they run. Lambdas made in a loop all see its last value.

Decorators

def logged(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(func.__name__) return func(*args, **kwargs) return wrapper
Apply a decorator@loggedShort for f = logged(f).

TrapWithout @functools.wraps(func), the wrapper hides the original function's name and docstring.

Iterators & generators

produce values one at a time

Iterable or iterator

Walk a list by handiter(nums)next(it)next raises StopIteration at the end.
Make an object iterable, or an iterator__iter____next__

TipEvery iterator is iterable, but not every iterable is an iterator. A for loop calls iter() once, then next().

TrapAn iterator is single-use. A list can loop many times, but a generator or a file object is empty after one pass.

Generators and yield

Yield a value and pauseyield aIt keeps its local variables.
Create a generatorgen = fib(10)The body doesn't run yet.

TipOnly one value exists at a time, so a generator walks a huge file or an endless stream in constant memory.

TrapA generator is single-pass. Once consumed it's empty, and it has no len() or indexing.

Classes & OOP

build objects and share behaviour

Classes, objects and variables

Set up each new object__init__(self, name)
Create an objectp = Person("Alice")
Share across instancesschool = "ABC"Write it in the class body.
Keep a value per objectself.name = name

Tipself is the object the method was called on. Python passes it in for you.

Traps.school = "x" hides the class variable for s only. A list as a class variable is shared by every instance.

Inheritance, super() and overriding

Inherit from a classclass Dog(Animal):Redefine a method to override it.
Call the next classsuper().speak()
Call a parent directlyAnimal.speak(self)
Accept varying argumentsdef add(self, *args):Python has no overloading. A later def replaces the earlier one.

TipUse inheritance for a true "is a" relationship. Otherwise prefer composition, where a class holds another.

Trapsuper() calls the next class in the MRO, Dog.__mro__, not simply the parent. In a diamond, a direct parent call can run a base class twice.

Private names and properties

Mark a name internalself._balancePython doesn't enforce it.
Stop subclasses clashingself.__balancePython renames it _Account__balance.
Control attribute access@property@x.setter

TrapPython has no private keyword. Name mangling isn't real privacy, because a._Account__balance still works.

Magic methods

Define + and ==__add____eq__
Give a string to users, or to developers__str____repr__
Work with len() and for loops__len____iter__

TrapDefining __eq__ without __hash__ makes instances unhashable.

Dataclasses

Generate the methods@dataclass
Default to a new listfield(default_factory=list)
Make it read-only, comparable or smallerfrozen=Trueorder=Trueslots=True

Tip@dataclass writes __init__, __repr__ and __eq__. __post_init__ runs after it, so validate there.

TrapFields with defaults must come after fields without them.

Metaclasses and monkey patching

Say what a metaclass isclass A(metaclass=M):The class of a class. The default is type.
Avoid a metaclass__init_subclass__Or use a class decorator.
Patch a method at runtimeCalc.add = loggedmock.patchpatch is for tests and restores the original.

TrapMonkey patching is risky in production. The change is global, invisible at the call site, and can break when the library changes.

Errors, cleanup & testing

catch errors, run tests

try, except, else, finally

except ValueError as e:Handle an error
else:Run the success path
finally:Clean up in every case
the block runsthe block is skipped

Tipfinally runs even after return. An exception no except matches triggers it, then keeps propagating.

TrapPython checks except blocks in order, so put the most specific first. A broad except Exception can hide bugs.

Custom exceptions and assert

Define and raise your ownclass E(Exception):raise E("bad age")
Keep the original causeraise E("x") from err
Check an assumptionassert n > 0, "msg"

TipIn a larger project, derive every error from one base exception, so callers can catch them all with one except.

Trappython -O removes every assert. Never use one to validate input or check permissions. Raise ValueError instead.

Context managers and with

Always close a filewith open(p) as f:
Write your own__enter____exit__@contextmanager

TipThe teardown runs even if the block raises. If __exit__ returns a true value, Python suppresses the exception.

Unit tests and debugging

Compare values in a testself.assertEqual(a, 5)
Check for an exceptionwith self.assertRaises(E):
Find and run testspython -m unittestpytest
Fake a slow dependencyunittest.mock
Pause and step throughbreakpoint()pdb.set_trace()
Log output you can switch offlogger.debug("x")

TipRead the full traceback first. It names the exception and the line. Prints give a quick look, and logging suits production.

Modules, files & venvs

organize code and dependencies

Modules, packages and imports

Tell a module from a packageA module is one .py file. A package is a directory of modules.
Import a module, or give it an aliasimport mathimport numpy as np
Import chosen names, or inside a packagefrom math import sqrtfrom .mod import fn
Set what import * brings in__all__ = ["fn"]

Tip__init__.py runs on first import, so keep it light. A directory without one is a namespace package.

TrapAvoid from math import *. It hides where each name came from.

File modes

Read, fail if missingopen(p, "r")
Write, emptying it firstopen(p, "w")
Append, create if neededopen(p, "a")
Create, fail if it existsopen(p, "x")

TipAdd b for bytes, or + to read and write, as in "r+". Open files in a with block so Python closes them.

Trap"w" truncates an existing file to empty. Use "x" to protect existing data.

Virtual environments

Create a venvpython -m venv .venv
Activate it on Unix or Windowssource .venv/bin/activate.venv\Scripts\activate
Record exact versions, then rebuild from thempip freeze > requirements.txtpip install -r requirements.txt

TipTreat it as disposable. Don't commit it or copy it to another machine. Recreate it from the requirements file.

Memory & performance

what CPython does, how to tune it

What runs when you start a program

Say if Python is compiled or interpretedBoth. CPython compiles source to bytecode, and its virtual machine interprets that.
Read the bytecodedis.dis(func)

TipPyPy instead uses a just-in-time (JIT) compiler.

TrapA .pyc file in __pycache__ only makes imports load faster, not run faster. The script you run directly compiles again each time.

Reference counts, GC and saving memory

Remove a namedel aAt zero references, CPython frees the object at once.
Free reference cycles nowgc.collect()The collector also runs by itself.
Drop each instance's __dict____slots__ = ["x", "y"]You then can't add other attributes.
Share equal stringssys.intern(s)
Know the cached integers-5 to 256 up to Python 3.14, and -5 to 1024 from Python 3.15.

Trapdel removes a name, not the object. Objects in a cycle keep each other's count above zero until the collector runs.

Make code faster

Find where the time goespython -m cProfile app.py
Test membership fastx in my_setA set takes O(1), and a list takes O(n).
Cache results@functools.lru_cache

TipMeasure first, then fix only the hot spots. Built-ins and NumPy run in compiled code.

Threads, processes & async

pick a concurrency model

Threads, processes and the GIL

thread 1thread 2process 1process 2time

CPU-bound work. Threads take turns holding the GIL. Each process has its own GIL, so processes run at once and finish sooner.

Run I/O-bound workThreadPoolExecutor(4)A waiting thread releases the GIL.
Run CPU-bound workProcessPoolExecutor(4)It costs more memory, and data between processes gets copied.
Say what the GIL isA CPython mutex. One thread runs bytecode at a time, which keeps built-in types safe.
Run threads without the GIL (3.13+)Use the optional free-threaded build, officially supported from 3.14. It's not the default.

TrapThreads don't speed up CPU-bound Python code, because only one runs at a time. The free-threaded build runs single-threaded code somewhat slower.

async and await

task Aawaittask Bawaittask Cawaittime

One thread. Solid bars run on the event loop one at a time, and dashed bars wait on I/O. The waits overlap, so the total is close to the slowest.

Define a coroutine, and await itasync def f():await f()
Start the event loopasyncio.run(main())
Run coroutines concurrentlyasyncio.gather(a(), b())
Cancel related tasks when one fails (3.11+)asyncio.TaskGroup()
Run blocking codeasyncio.to_thread(f)

TipCalling a coroutine function only returns a coroutine object. Nothing runs until you await or schedule it.

TrapA coroutine gives up control only at an await. A blocking call or a long CPU loop freezes every other task.

Found this useful? Pass it on.
Pro · $10/mo

This sheet stays free. With Pro, you can:

  • Every answer on this sheet. Read the full answer to all 59 questions it links, not just the 7 free ones.
  • My sheet. The cards you star and the ones you miss in Quiz me, from every sheet, on one page to quiz and print.
  • PDFs. Download this sheet and My sheet, laid out for A4, with every question a link to its answer.
  • The rest of Pro. Every answer in the question library, and 1,000 AI credits a month.