Python Interview Questions — Practice with Real Quiz

Reviewed by Mark Dickie · Last updated

Python is a high-level, general-purpose programming language known for readable syntax, dynamic typing, and a large standard library. For interviews, you should be comfortable with mutable vs. immutable types, how arguments are passed, list/dict comprehensions, decorators, context managers, generators, and the GIL. Most rounds also test your grasp of built-in data structures, common algorithms, and Python-specific idioms like __init__ vs. __new__ or the difference between is and ==.

TopicWhat to study
Data structuresLists, dicts, sets, tuples, deque; time complexity of common operations
Object-oriented programming__init__, __new__, inheritance, MRO, dunder methods, super()
Functions & scopeClosures, decorators, *args/**kwargs, LEGB rule
IterationGenerators, yield, itertools, lazy evaluation
Concurrencythreading vs. multiprocessing, the GIL, asyncio basics
Memory & performanceReference counting, garbage collection, sys.getsizeof, profiling

What does a Python interview typically test?

  1. Core language semantics: mutable defaults in function signatures, late binding in closures, shallow vs. deep copy behavior.
  2. Data structure manipulation: reversing a linked list, detecting cycles, implementing an LRU cache with OrderedDict.
  3. Algorithmic problem-solving: two-pointer techniques, sliding windows, dynamic programming on arrays and strings.
  4. Python idioms and internals: how decorators stack, what __enter__/__exit__ do, when is returns True for small integers (interning).
  5. Real-world coding tasks: parsing a CSV without pandas, writing a retry decorator, building a simple rate limiter.

How should you prepare for a Python coding round?

Focus on writing clean, idiomatic code under time pressure. Practice problems where the language gives you a shortcut (a comprehension, a slice, a collections helper) and problems where you have to build the machinery yourself. Understand the trade-offs: when a generator saves memory, when defaultdict is cleaner than dict.setdefault, when dataclasses replace boilerplate __init__ methods. Interviewers want to see that you know both the fast path and the underlying mechanics.

Key facts

  • Tarmac has 201 Python interview questions on this topic, 10 of them on this page, at difficulty 1–5 of 5.
  • Tarmac last reviewed these Python interview questions on 18 August 2026.

At a glance

Questions10 shown · 201 in the bank
Difficulty1–5 of 5
FormatsTrue / false, Coding exercise, Find the bug, Flashcard, Ordering, Code output, Multiple choice, Multiple answer, Fill in the blank, Short answer
Interactive1 run your code against tests, in the app

What you'll review

  1. type hints
  2. slicing
  3. classmethod staticmethod
  4. try except else finally
  5. pytest

Practice questions

Python/typing/type-hints

In Python, adding type hints to a function causes the interpreter to raise a TypeError at runtime if the caller passes a value of the wrong type.#

Options

Show answer

False — Python type hints are not enforced at runtime. The interpreter treats annotations as metadata only; no TypeError is raised for mismatched types. Static analysis tools like mypy can catch type errors before execution, but the Python runtime itself does not validate them.

Why:

Python type hints are purely informational by default. The interpreter does not enforce them at runtime — passing a value of the wrong annotated type will not raise any error unless an external tool (like mypy) or a library (like pydantic) explicitly validates types. This is why Python's type system is described as 'gradual' and optional.

Python/testing-idioms/slicing

Implement chunk(values, size): split a list into consecutive sublists of length size, with the final chunk shorter if the list doesn't divide evenly. An empty list returns []. You may assume size >= 1.#

Starter code

def chunk(values, size):
    # TODO: slice `values` into runs of `size`
    return [values]

Your solution must pass

  • even split
  • uneven tail

This one is written and run, not read. Solve it in the app and your code is executed against these tests and the hidden ones.

Python/typing/type-hints

The function below is intended to return the first element of a list if the list is non-empty, otherwise return None. A type checker like mypy will report an error on one specific line. Which line contains the bug?#

from typing import Optional

def first(items: list[int]) -> Optional[int]:
    if items:
        result: str = items[0]
        return result
    return None
Show answer

The bug is on line 5.

Why:

Line 5 annotates result as str, but items[0] is of type int (since items is list[int]). A static type checker will flag this as an incompatible assignment (int cannot be assigned to str). This also makes return result return a value whose declared type (str) is incompatible with the function's return type Optional[int]. The fix is to change the annotation to int (or remove it altogether and let the type checker infer it).

Python/oop/classmethod-staticmethod

What is the difference between @classmethod and @staticmethod?#

Show answer

@classmethod receives the class as its first argument (cls) — useful for alternative constructors and for code that must respect subclassing. @staticmethod receives no implicit first argument; it's just a plain function namespaced on the class, with no access to cls or self.

Why:

classmethod binds to the class (cls) so it sees the actual subclass at call time; staticmethod takes neither self nor cls. Reach for classmethod when you need a factory like cls(...), and staticmethod for a utility that just lives on the class.

Python/errors-control-flow/try-except-else-finally

For a try block that completes WITHOUT raising, order the clauses by when their bodies run.#

Put these in order

Show answer

On the no-exception path the clauses run in this order:

  1. try: body
  2. else: body (runs only if no exception)
  3. finally: body

The try body runs to completion, then else runs because the block succeeded, then finally always runs last. The except clause is skipped because nothing was raised.

Why:

On the no-exception path the try body runs to completion, then else (its whole point — code that should run only when try succeeded), then finally always runs last. except is skipped because nothing was raised.

Python/typing/type-hints

What is printed by the following code? Assume the module is run as __main__ and Node is fully defined before get_type_hints is called.#

from __future__ import annotations
from typing import Optional, get_type_hints

class Node:
    def __init__(self, val: int, next: Optional[Node] = None) -> None:
        self.val = val
        self.next = next

hints = get_type_hints(Node.__init__)
print(hints['next'])
Show answer
typing.Optional[__main__.Node]
Why:

Python's typing.get_type_hints() resolves forward references and applies __future__ annotations. When from __future__ import annotations is active, ALL annotations are stored as strings (PEP 563 postponed evaluation). get_type_hints() then resolves them at call time using the provided (or inferred) global namespace. Here the forward reference 'Node' is resolved by get_type_hints() to the actual Node class, so hints['next'] returns typing.Optional[Node]. Printing that gives typing.Optional[__main__.Node] (or the equivalent module path). The __annotations__ dict, by contrast, would still hold the raw string.

Python/typing/type-hints

You want to write a generic function first_and_last that accepts any subtype of Sequence[int] and returns a value of that same exact subtype (not just Sequence[int]). Which TypeVar declaration correctly models this constraint?#

Options

Show answer

T = TypeVar('T', bound=Sequence[int])

Why:

TypeVar('T', bound=Sequence[int]) is the correct choice. A bound constrains T so that at every call site the type-checker resolves T to the specific concrete subtype passed in (e.g., list[int], tuple[int, ...]), preserving that exact type through the return annotation. Option a is invalid: covariant=True on a free TypeVar (not used as a parameter of a generic class) is meaningless for a standalone function — mypy and pyright ignore it — and without a bound there is no constraint to Sequence[int] at all. Option c uses constraints (an explicit list of allowed types), not a bound. A constrained TypeVar forces T to collapse to exactly one of the listed types at the call site, so passing a custom MySequence(Sequence[int]) subclass that isn't in the list would be rejected — this is more restrictive and does not model "any subtype". Option d constrains T to int | str | float, which has nothing to do with Sequence[int] and would reject any sequence argument. The idiomatic, correct tool for "any subtype of X, preserving that subtype" in a generic function is bound=X.

Python/typing/type-hints

Which of the following statements about Python's typing.Protocol (PEP 544) are correct? Select all that apply.#

Options

Pick every one that applies.

Show answer

The correct statements are: (a) a class satisfies a Protocol through structural compatibility alone — no explicit inheritance needed; (c) a Protocol can inherit from another Protocol to compose required members; and (d) using a plain (non-@runtime_checkable) Protocol in an isinstance() check raises TypeError at runtime. @runtime_checkable checks only attribute presence, not signatures, and Protocol methods lack any runtime enforcement analogous to abc.abstractmethod.

Why:

typing.Protocol (PEP 544) enables structural subtyping: a class satisfies a Protocol if it has the required attributes/methods, regardless of inheritance hierarchy. Protocol composition is supported — a Protocol may inherit from one or more other Protocols, merging their required members. Without @runtime_checkable, passing a Protocol to isinstance() raises TypeError at runtime (Python explicitly forbids it). Option b is wrong because @runtime_checkable only checks for attribute/method presence, not full signature compatibility. Option e is wrong at the runtime level: unlike abc.abstractmethod, Protocol methods with no default body are not enforced at runtime — Python will happily instantiate a class that omits them; enforcement is purely static (type-checker level).

Python/testing-idioms/pytest

Complete the pytest test below so it is parametrized to run three times — once for each (input_val, expected) pair — and uses the _____ decorator. Inside the test, the match keyword argument to pytest.raises accepts a regular expression pattern that is matched against the string representation of the exception. Fill in the decorator name and a valid regex pattern (for blank _____) that matches the AttributeError raised when calling .upper() on an integer.#

Show answer

Complete the pytest test below so it is parametrized to run three times — once for each (input_val, expected) pair — and uses the **parametrize** decorator. Inside the test, the match keyword argument to pytest.raises accepts a regular expression pattern that is matched against the string representation of the exception. Fill in the decorator name and a valid regex pattern (for blank upper) that matches the AttributeError raised when calling .upper() on an integer.

import pytest

@pytest.mark.**parametrize**(
    "input_val, expected",
    [("hello", "HELLO"), ("world", "WORLD"), ("", "")],
)
def test_upper(input_val, expected):
    assert input_val.upper() == expected

def test_bad_type():
    with pytest.raises(AttributeError, match=r"**upper**"):
        (42).upper()
Why:

The @pytest.mark.parametrize decorator (spelled exactly that way — American English, no 'd' at the end) drives a test function with multiple argument sets, generating a separate test item for each tuple. The match argument to pytest.raises() accepts a regular expression pattern that is searched against the string representation of the raised exception using re.search. When (42).upper() is called, CPython raises AttributeError: 'int' object has no attribute 'upper'. A valid regex pattern for the second blank must actually match that message — for example, upper or has no attribute 'upper' — so that re.search(pattern, "'int' object has no attribute 'upper'") returns a match object rather than None.

Python/typing/type-hints

In Python's typing module (3.10+), what is the primary purpose of ParamSpec, and why is it necessary for correctly typing higher-order functions such as decorators — specifically, what limitation of plain TypeVar does it address?#

Show answer

ParamSpec captures the full parameter specification (names, types, defaults, *args/**kwargs) of a callable so a decorator's return type can reflect the exact signature of the wrapped function. A plain TypeVar can only represent a single type (e.g., the return type); it cannot encode the relationship between the decorator wrapper's parameters and the wrapped function's parameters. Without ParamSpec, the type checker cannot verify that arguments passed to a decorated function match the original function's signature.

Why:

ParamSpec (PEP 612, Python 3.10+) captures the full parameter specification (positional + keyword) of a callable so that higher-order functions (like decorators) can preserve the exact call signature of the wrapped function in the type system. TypeVar can only capture the return type or a single type — it cannot represent the relationship between the arguments of the outer wrapper and the inner function. Concatenate[X, P] is used alongside ParamSpec to prepend additional parameters. Without ParamSpec, a generic decorator typed with only TypeVar loses the wrapped function's parameter types, making static analysis blind to argument errors in the decorated call.

Sources

The official documentation these questions are checked against:

Related interview questions

The other 191 questions

This page shows 10. A free account opens the rest and marks your answers, which a page of answers can't do. Miss one and it returns a few days later, then a week after that, until you stop missing it.

Start free

Free · the whole bank · 100 graded answers per 30 days · written feedback and job-ad quizzes on the paid plan

What moved, monthly

One email a month when the bulletin comes out: what moved in the markets we track, and the new question topics we published. Confirm your address to join. Unsubscribe any time.