Skip to content

Python Developer interview questions

100 real questions with model answers and explanations for Middle candidates.

See a Python Developer resume example

Practice with flashcards

Spaced repetition · Hunter Pass

Questions

decoratorsfunctools

functools.wraps preserves the decorated function's identity and exposes the original function for introspection.

  • Without it, __name__ and __doc__ belong to the wrapper, so tracebacks, logs, documentation tools, and debugging show the wrong function.
  • Libraries that dispatch on function metadata can misbehave, including pickling, functools.singledispatch, and framework route registration.
  • wraps copies __name__, __doc__, __module__, and __dict__, then sets __wrapped__ so inspect.unwrap can reach the original function.

Why interviewers ask this: The interviewer checks whether the candidate has debugged the consequences of missing metadata rather than treating wraps as a cargo-cult line.

resiliencedecorators

A decorator with arguments uses nested closures to carry its options through to call time.

  • The outer factory receives arguments such as times=3 and returns the actual decorator.
  • The decorator receives the function and returns a wrapper, which can access both the function and the options through closures.
  • Parentheses are required even for defaults unless both forms are supported by checking whether the first positional argument is callable; the simpler alternative is to require and document parentheses.

Why interviewers ask this: A strong answer explains the closure chain and why the extra call layer exists, not just reproduces memorized nesting.

decorators

Stacked decorators are applied from the function outward and entered in the opposite order at call time.

  • Application is bottom-up: the decorator closest to def wraps the function first, and every decorator above wraps the previous result.
  • Execution is top-down: the outermost wrapper runs first and delegates inward.
  • The order affects behavior: a cache outside an auth check can cache responses for unauthorized users, while the reversed order makes every cache hit pass through auth.

Why interviewers ask this: The auth-plus-cache example shows the candidate understands ordering as a real correctness issue, not trivia.

error-handling

__exit__ always gets a chance to clean up and can decide whether an exception leaves the with block.

  • It receives the exception type, value, and traceback, or three None values when the block finishes normally.
  • A truthy return suppresses the exception and continues execution after the with block, as contextlib.suppress does.
  • Returning None or False propagates the exception, while cleanup in __exit__ still runs either way.

Why interviewers ask this: The return-value-controls-suppression detail separates people who have implemented context managers from those who only consume them.

generatorscontext-managers

contextlib.contextmanager maps the two sides of a single yield to context entry and exit.

  • Code before yield acts as __enter__, and the yielded value becomes the target after as.
  • Code after yield acts as __exit__, so yield must be wrapped in try/finally to guarantee cleanup when the with body raises.
  • The exception is thrown into the generator at yield, and catching it without re-raising suppresses it just like returning True from __exit__.

Why interviewers ask this: The try/finally requirement is the classic bug this question hunts for, and strong candidates name the throw-into-generator mechanism.

ci-cdgenerators

A generator suspends and resumes one preserved execution frame, enabling item-by-item processing.

  • Calling a generator function runs none of its body and returns a generator object with a suspended frame.
  • Each next() resumes execution until yield, returns the value, and suspends again while preserving locals and the instruction pointer.
  • A pipeline such as parse(filter(read_lines(f))) can process each item through every stage and keep memory usage flat for a multi-gigabyte log.

Why interviewers ask this: The interviewer listens for the suspended-frame mental model and a concrete streaming use case rather than yield returns multiple values.

generatorscomprehensions

Choose a list for reusable materialized data and a generator for lazy single-pass consumption.

  • A list comprehension builds everything immediately, which suits length checks, multiple passes, indexing, and sorting.
  • A generator expression yields lazily and works well with sum or any, where short-circuiting can avoid unnecessary work.
  • A generator is wrong when the consumer iterates twice, because the second pass silently produces no items and raises no error.

Why interviewers ask this: Naming the silent-exhaustion bug shows the candidate has been burned by it, which is exactly the practical depth being probed.

generators

yield from delegates the full generator protocol rather than only forwarding yielded values.

  • For plain iteration it looks like a for loop with yield, but send() and throw() from the consumer are passed into the subgenerator.
  • The subgenerator's return value becomes the value of the yield from expression, supporting generators that produce both a stream and a final result.
  • This return-value plumbing powered pre-async coroutine frameworks, while a manual loop breaks the two-way communication.

Why interviewers ask this: Mentioning delegation of send and the captured return value distinguishes real understanding from syntax sugar for a loop.

hypothesis-testinglambda

Lambdas in a loop share one captured variable, so they observe its final value when called later.

  • Closures capture variables rather than snapshots of their values, and the loop leaves its shared variable set to the last item.
  • A default argument such as lambda x=x: ... binds the current value at definition time; functools.partial can store it explicitly instead.
  • Without that binding, callbacks or handler dictionaries built in a loop make every handler act on the last item.

Why interviewers ask this: The interviewer wants late binding named as the cause and the default-argument idiom as the fix, ideally with the callback registration context.

gotchas

Default arguments are evaluated once at function definition, which is dangerous for mutable objects but useful for deliberate snapshots.

  • def f(items=[]) reuses one list across calls, so appended values leak from one invocation to another.
  • The standard fix is a None sentinel followed by items = [] if items is None else items inside the function.
  • Definition-time binding can intentionally snapshot a loop value or cache an expensive lookup, although explicit caching mechanisms are preferable for the latter.

Why interviewers ask this: Knowing the mechanism cuts both ways, and the strong candidate can name a legitimate use of def-time evaluation, not just the bug.

descriptorstyping

Descriptors centralize managed attribute behavior through methods on a class attribute's type.

  • A descriptor defines __get__, __set__, or __delete__, and instance attribute access is routed through those methods via the class.
  • property is a built-in descriptor that maps reads and writes to functions; functions are also descriptors whose __get__ creates bound methods.
  • A custom descriptor is useful when logic such as field validation or unit conversion repeats across many attributes and would otherwise require many similar properties.

Why interviewers ask this: Connecting property and bound methods to the same protocol shows systemic understanding, and the reuse criterion shows practical judgment.

slots

__slots__ trades dynamic instance attributes for fixed storage and lower per-instance memory use.

  • It replaces each instance's __dict__ with storage for named attributes, significantly reducing memory and slightly speeding attribute access.
  • Arbitrary runtime attributes are unavailable, weak references require an explicit __weakref__ slot, and a non-slotted parent restores __dict__ during inheritance.
  • It is worthwhile for millions of instances such as AST nodes or tick data points, but ordinary domain classes often benefit more from flexibility.

Why interviewers ask this: The when-it-pays-off criterion of massive instance counts separates informed use from premature optimization.

decoratorsdataclasses

dataclass generates common data-object methods from annotated fields and provides safe controls for immutability and defaults.

  • It creates __init__, __repr__, and __eq__, with optional __hash__ and ordering methods, removing repetitive assignment-heavy constructors.
  • frozen=True rejects attribute assignment, enabling hashability and safe use as dictionary keys.
  • field(default_factory=list) calls a factory for every instance, avoiding the shared mutable default bug of a bare list.

Why interviewers ask this: Tying default_factory back to the mutable default pitfall shows the candidate understands why the API is shaped this way.

conflict

== compares values through __eq__, while is checks whether two references point to the same object.

  • Equal strings built at runtime can be distinct objects even though == returns True.
  • CPython interns small integers and some compile-time strings, but behavior can change for concatenated or user-provided strings and across implementations.
  • Use is for singletons such as None, True, False, and custom sentinel objects, never as a value comparison based on interning.

Why interviewers ask this: Restricting is to identity semantics and singletons, while explaining why interning tests are unreliable, is the expected mature answer.

copying

Shallow copying duplicates only the container, while deep copying recursively duplicates its object graph.

  • copy.copy creates a new outer object whose elements remain the same references, so changing a nested list through the copy also changes the original.
  • copy.deepcopy recursively copies the graph and uses a memo dictionary to handle cycles.
  • A shallow config copy can leak a nested tenant change to everyone, but deep-copying huge structures in hot paths can cost more than redesigning the code to avoid mutation.

Why interviewers ask this: The interviewer wants the reference-sharing mechanism plus awareness that deepcopy has a real performance cost, not just definitions.

gc

CPython needs cycle collection because reference counting alone cannot release unreachable objects that reference each other.

  • Reference counting frees an object when its count reaches zero, but members of a cycle keep one another above zero.
  • The generational cyclic collector periodically finds these unreachable groups; __del__ complicates this because finalization order in a dying cycle is ambiguous.
  • Such cycles historically remained uncollected and are handled today with potentially surprising finalizer order, so resource cleanup is better done with context managers or weakref.finalize.

Why interviewers ask this: The cycle example and the preference for context managers over __del__ show the candidate understands memory management operationally.

gil

The GIL protects CPython interpreter internals, not the thread safety of application-level operations.

  • It allows only one thread to execute Python bytecode at a time, preventing corruption of interpreter state such as reference counts.
  • Multi-bytecode actions such as check-then-update or += on a shared attribute can still interleave, causing races and lost updates.
  • The GIL is released around blocking I/O and inside many C extensions, which is why threads can still improve I/O-bound workloads.

Why interviewers ask this: The distinction between interpreter safety and application-level thread safety is the core misconception this question is designed to expose.

concurrencypython

Threads overlap blocking waits in I/O-bound work but serialize CPU-bound Python execution under the GIL.

  • A thread releases the GIL during a blocking socket read or database call, allowing other threads to run and increasing throughput across concurrent waits.
  • CPU-bound threads do not wait and instead contend for one GIL, so work is serialized and can be slower because of switching overhead.
  • CPU-heavy work can use multiprocessing, C extensions such as numpy that release the GIL internally, or another runtime.

Why interviewers ask this: A crisp mechanical explanation of GIL release during I/O, rather than a memorized rule, is what earns full marks here.

typing

Type hints and mypy should be introduced incrementally while protecting every module that has already become typed.

  • Start with permissive mypy settings, add stricter per-module overrides, and gate CI so clean modules cannot regress.
  • Type all new code and prioritize the shared core, models, and utilities, where annotations catch the most downstream mistakes.
  • Avoid a single annotation change across thousands of files because the diff becomes stale in review while feature work continues.

Why interviewers ask this: The ratchet strategy of per-module strictness with CI enforcement is the practical pattern that distinguishes people who have actually done this migration.

typing

Type safety usually leaks through Any and through unchecked escape hatches such as casts and ignores.

  • Untyped libraries, json.loads results, and unannotated functions produce Any, which propagates silently and disables downstream checking.
  • Tighten mypy with disallow_untyped_defs and warn_return_any, then parse external data at the boundary into TypedDict, dataclass, or pydantic models instead of passing raw dictionaries.
  • Require a reason for casts and type: ignore directives, and search for them periodically so they can be removed.

Why interviewers ask this: Identifying Any propagation as the failure mode and typed boundaries as the fix shows real mypy operating experience.

Locked questions

  • 21

    Protocol versus ABC: how do you choose between structural and nominal typing for an interface?

    typestyping
  • 22

    How do keyword-only arguments improve an API, and how do you define them?

    api
  • 23

    Explain LEGB name resolution and when you actually need nonlocal.

    scope
  • 24

    What is the difference between an iterable and an iterator, and what bug does confusing them cause?

    iteration
  • 25

    How do you design exceptions for a library, and what does raise from add?

    designerror-handling
  • 26

    What contract must __eq__ and __hash__ satisfy together, and what breaks when you violate it?

    dunder
  • 27

    Why is building a string with += in a loop a problem, and what do you use instead?

  • 28

    You are writing a Money class. Which dunder methods do you implement and what details matter?

    dunder
  • 29

    How do circular imports happen and what are your options for breaking them?

    modules
  • 30

    Why is work executed at import time dangerous, and what belongs under if __name__ == '__main__'?

    modules
  • 31

    Threading, multiprocessing, or asyncio: walk me through your decision for a new workload.

    asyncconcurrency
  • 32

    What does await actually do, and why does one blocking call freeze an entire asyncio application?

    async
  • 33

    You must call a blocking library from async code. What are your options?

    async
  • 34

    How does asyncio.gather handle failures, and when do you use return_exceptions or a TaskGroup instead?

    async
  • 35

    Give a concrete example of a race condition in threaded Python and how you eliminate it.

    concurrencypython
  • 36

    What breaks when you move code from threads to multiprocessing, and how do fork and spawn start methods differ?

    concurrency
  • 37

    How do you use concurrent.futures for parallel work, and what does it give you over raw threads?

    concurrency
  • 38

    Sketch a producer-consumer setup with queue.Queue. Why is the queue enough without extra locks?

    data-structures
  • 39

    How do timeouts and cancellation work in asyncio, and what must a well-behaved task do when cancelled?

    resilienceasync
  • 40

    When do you move work out of the request path into a task queue like Celery, and why must those tasks be idempotent?

    task-queuesidempotencydata-structures
  • 41

    What do pytest fixtures give you over setUp methods, and how do fixture scopes work?

    pytestfixtures
  • 42

    What is conftest.py and how do you keep it from becoming a dumping ground?

    pytest
  • 43

    Why does mock.patch('module_a.requests.get') sometimes fail to mock anything, and what is the rule?

    mocking
  • 44

    MagicMock accepts any call with any arguments. Why is that dangerous, and how does autospec help?

    mocking
  • 45

    How does pytest.mark.parametrize change how you structure tests, and what are ids for?

    pytestparametrize
  • 46

    How do you test that code raises the right exception, beyond just the type?

    error-handling
  • 47

    Your code depends on current time and randomness. How do you make its tests deterministic?

    testing
  • 48

    The team hit 100 percent test coverage. What does that number guarantee, and what does it not?

    coverage
  • 49

    What changes when testing async code with pytest, and what classic mistake produces a test that always passes?

    pytestasyncownership
  • 50

    For code using the database, when do unit tests with SQLite in memory stop being good enough?

    unitdatabasememory
  • 51

    How does TDD actually work in your practice, and where do you deviate from strict red-green-refactor?

    refactoringtdd
  • 52

    Your CI suite has flaky tests. What are the usual root causes in Python projects and how do you eliminate them systematically?

    flakysystem-designpython
  • 53

    requirements.txt with loose versions keeps breaking your builds. What does a proper dependency setup look like?

    dependencies
  • 54

    How does version pinning strategy differ between a library you publish and an application you deploy?

    deployment
  • 55

    Why are virtual environments non-negotiable, and what actually goes wrong when someone installs into system Python?

    system-designpythonvenv
  • 56

    What is pyproject.toml and what fragmented mess did it replace?

    packaging
  • 57

    Why do many projects put their package under a src directory instead of the repository root?

    packaging
  • 58

    pip reports a dependency conflict between two packages you need. How do you actually resolve it?

    dependenciespip
  • 59

    How do you set up formatting and linting so code style never becomes a review topic?

    linting
  • 60

    You need to upgrade a core dependency across a major version with breaking changes. Describe your process.

    dependenciesconcurrencyversioning
  • 61

    How does dependency injection with Depends work in FastAPI, and how does it make tests easier?

    injectiondependenciestesting
  • 62

    What role do Pydantic models play at API boundaries, and why not pass raw dicts through the service?

    validation
  • 63

    In Django ORM, when do you need select_related versus prefetch_related, and how do you catch the queries you missed?

    ormqueriesframeworks
  • 64

    How do you ship a Django migration that changes a column on a large, live table without downtime?

    migrationsschemaframeworks
  • 65

    Why does Flask documentation push the application factory pattern instead of a module-level app object?

    documentationframeworks
  • 66

    WSGI versus ASGI: what is the actual difference, and when does it matter for your framework choice?

    wsgi-asgi
  • 67

    How do you manage SQLAlchemy session lifecycle in a web application, and what goes wrong when you get it wrong?

    ormsessions
  • 68

    What lazy loading surprises does SQLAlchemy have, and how do you load relationships deliberately?

    ormlazy-loading
  • 69

    Where do you put transaction boundaries in a web application, and why is autocommit-per-statement not enough?

    transactions
  • 70

    What makes error responses in an API well-designed, and how do you keep them consistent across a Python service?

    designapipython
  • 71

    Sketch how token-based authentication works in a FastAPI or Django service, and name the classic implementation mistakes.

    authtokensframeworks
  • 72

    FastAPI BackgroundTasks or Celery: how do you choose for work that should happen after the response?

    task-queuesbackground-jobsframeworks
  • 73

    Offset pagination worked fine until the table grew and users complained about duplicates between pages. What is happening and what do you do?

    pagination
  • 74

    How do you handle a large file upload or a large export in a Python web service without exhausting memory?

    soft-skillsmemorypython
  • 75

    An endpoint is slow. Walk me through profiling it with cProfile and interpreting the output.

    profilingendpoints
  • 76

    What mistakes make microbenchmarks with timeit lie to you?

    ownershipprofiling
  • 77

    Before reaching for C extensions or rewrites, what cheap Python performance wins do you check first?

    python
  • 78

    How do you hunt a memory leak in a Python process with tracemalloc, and where do leaks usually hide?

    memoryconcurrencypython
  • 79

    A long-running service's memory climbs slowly over days. How do you attack this in production?

    memory
  • 80

    When does functools.lru_cache help, and what are the ways it hurts you?

    cachingfunctools
  • 81

    Your numeric processing loop in pure Python is 100x too slow. What is your escalation path?

    concurrencypythonescalation
  • 82

    How do you use pdb or breakpoint() effectively, including on an exception that already happened?

    error-handling
  • 83

    A bug reproduces only in production, never locally. How do you investigate without a debugger?

    debugging
  • 84

    A test or job fails once every fifty runs. What is your methodology for pinning down an intermittent failure?

  • 85

    You see a traceback with two exceptions separated by 'during handling of the above exception, another exception occurred'. How do you read it?

    error-handling
  • 86

    What does good logging discipline look like in a Python service, compared to print statements?

    debuggingloggingpython
  • 87

    How do you design the public API of a Python module or package so it stays maintainable?

    designapipython
  • 88

    Composition versus inheritance in Python: where do you draw the line, and what goes wrong with mixin-heavy designs?

    ooppythondesign
  • 89

    When is duck typing enough, and when do you formalize an interface with a Protocol or ABC?

    typestyping
  • 90

    Explain EAFP versus LBYL with a case where the look-before-you-leap version is actually buggy.

    idioms
  • 91

    A module could be five functions or a class. How do you decide, and where does over-engineering creep in?

  • 92

    You must change behavior inside a 500-line function nobody fully understands. How do you proceed safely?

  • 93

    Beyond style, what do you actually look for when reviewing a Python pull request?

    code-reviewpython
  • 94

    A senior colleague harshly criticizes your pull request, and some of the criticism is right. How do you respond?

    code-review
  • 95

    You and a teammate disagree on an implementation approach and both have reasonable arguments. How do you resolve it?

    conflict
  • 96

    Mid-sprint you realize your feature will miss the deadline. What do you do?

    agileestimation
  • 97

    You join a team with a large unfamiliar Python codebase. What do your first two weeks look like?

    joinspython
  • 98

    You must fix a bug in legacy code that has no tests. What is your sequence?

    testing
  • 99

    How do you decide whether to adopt a new Python feature or tool, say pattern matching or a new package manager, in a team codebase?

    decision-makingnpmpython
  • 100

    Your deploy just caused a production incident. Walk me through your actions from the first alert.

    deploymentalertingincidents