codejump

Interview questionsPython

Python interview questions and answers

30 Python questions picked from Codejump's quizzes, from the basics a junior is expected to know to the details that trip up senior candidates. Each one comes with its answer and an explanation — try to answer before you open it.

Junior level Python questions

  1. 1.What is the output of the following code?

    javascript
    import asyncio
    
    async def compute():
        await asyncio.sleep(0)
        return 42
    
    async def main():
        result = await compute()
        print(result)
    
    asyncio.run(main())
    • A.<coroutine object compute at 0x...>
    • B.TypeError: 'coroutine' object is not callable
    • C.None
    • D.42
    Show answer

    Answer: D. 42

    await compute() runs the compute coroutine to completion and evaluates to whatever it returns — 42. result correctly captures that value, and print(result) outputs 42.

    From the quiz Coroutines & asyncio.run

  2. 2.What is the output of the following code?

    javascript
    from collections import deque
    
    d = deque([1, 2, 3])
    d.appendleft(0)
    d.append(4)
    d.popleft()
    
    print(list(d))
    • A.[0, 1, 2, 3, 4]
    • B.[1, 2, 3, 4]
    • C.[1, 2, 3]
    • D.[0, 1, 2, 3]
    Show answer

    Answer: B. [1, 2, 3, 4]

    Start: [1, 2, 3]. appendleft(0) adds to the front: [0, 1, 2, 3]. append(4) adds to the end: [0, 1, 2, 3, 4]. popleft() removes from the front (0): [1, 2, 3, 4].

    From the quiz Collections Basics

  3. 3.What is the output of the following code?

    javascript
    class InsufficientFundsError(Exception):
        def __init__(self, balance, amount):
            self.balance = balance
            self.amount = amount
            super().__init__(f"Cannot withdraw {amount}, balance is {balance}")
    
    try:
        raise InsufficientFundsError(50, 100)
    except InsufficientFundsError as e:
        print(str(e))
        print(e.amount)
    • A.TypeError: Exception.__init__() takes 1 positional argument
    • B.Cannot withdraw 100, balance is 50 then 100
    • C.InsufficientFundsError then 100
    • D.Cannot withdraw 100, balance is 50 then 50
    Show answer

    Answer: B. Cannot withdraw 100, balance is 50 then 100

    Custom exceptions subclass Exception and can define their own __init__ with additional attributes alongside calling super().__init__(message) to set the standard exception message. str(e) returns that formatted message; e.amount accesses the custom attribute directly, 100.

    From the quiz Try, Except & Finally

  4. 4.What is the output of the following code?

    javascript
    from functools import reduce
    
    nums = [1, 2, 3, 4]
    result = reduce(lambda acc, x: acc + x, nums)
    print(result)
    • A.24
    • B.TypeError: reduce expected at least 3 arguments, got 2
    • C.10
    • D.[1, 2, 3, 4]
    Show answer

    Answer: C. 10

    reduce(function, iterable) applies function cumulatively, left to right: acc=1, x=2 → 3; acc=3, x=3 → 6; acc=6, x=4 → 10. The final accumulated value, 10, is returned.

    From the quiz map, filter & reduce

  5. 5.What is the output of the following code?

    javascript
    a = [1, 2, 3]
    b = [1, 2, 3]
    c = a
    
    print(a == b)
    print(a is b)
    print(a is c)
    print(a == c)
    • A.True, True, True, True
    • B.False, False, True, True
    • C.True, False, False, True
    • D.True, False, True, True
    Show answer

    Answer: D. True, False, True, True

    == compares values (via __eq__), while is compares object identity (whether both names point to the exact same object in memory). a and b are two separate list objects with equal contents, so a == b is True but a is b is False. c = a makes c refer to the same object as a, so a is c is True, and naturally a == c is also True.

    From the quiz Core Concepts

  6. 6.What is the output of the following code?

    javascript
    class Account:
        def __init__(self, balance):
            self._balance = balance
            self.__pin = "1234"
    
    a = Account(100)
    print(a._balance)
    print(a.__pin)
    • A.100 then 1234
    • B.100 then AttributeError
    • C.100 then _Account__pin
    • D.AttributeError then AttributeError
    Show answer

    Answer: B. 100 then AttributeError

    A single leading underscore (_balance) is only a naming convention signaling internal use — Python enforces nothing, so a._balance works fine. A double leading underscore (__pin) triggers name mangling: Python internally rewrites it to _Account__pin, so accessing it as a.__pin from outside the class raises AttributeError, since an attribute literally named __pin no longer exists.

    From the quiz Classes & Instances

  7. 7.What is the output of the following code?

    javascript
    import asyncio
    
    async def slow():
        await asyncio.sleep(0.1)
        return "slow done"
    
    async def fast():
        await asyncio.sleep(0.01)
        return "fast done"
    
    async def main():
        results = await asyncio.gather(slow(), fast())
        print(results)
    
    asyncio.run(main())
    • A.TypeError: gather() takes at most 1 argument
    • B.Unpredictable order, depending on which coroutine finishes first.
    • C.['fast done', 'slow done']
    • D.['slow done', 'fast done']
    Show answer

    Answer: D. ['slow done', 'fast done']

    asyncio.gather(*coros) runs all given coroutines concurrently, but its returned results list is always ordered to match the order the coroutines were passed in, regardless of which one actually finishes first. Since slow() was passed first and fast() second, the results are ['slow done', 'fast done'], even though fast() completes sooner in real time.

    From the quiz Coroutines & asyncio.run

  8. 8.What is the output of the following code?

    javascript
    matrix = [[1, 2, 3], [4, 5, 6]]
    flat = [num for row in matrix for num in row if num % 2 == 0]
    print(flat)
    • A.[2, 6]
    • B.[[2], [4, 6]]
    • C.[2, 4, 6]
    • D.[1, 3, 5]
    Show answer

    Answer: C. [2, 4, 6]

    This nested comprehension is equivalent to: Iterating [1, 2, 3] then [4, 5, 6], keeping only even numbers: 2, 4, 6.

    From the quiz Collections Basics

  9. 9.What is the output of the following code?

    javascript
    def f():
        try:
            return "from try"
        finally:
            print("finally ran")
    
    result = f()
    print(result)
    • A.from try then finally ran
    • B.finally ran then from try
    • C.TypeError: 'finally' block cannot coexist with 'return'
    • D.finally ran only — the return in try is overridden and nothing is returned.
    Show answer

    Answer: B. finally ran then from try

    Even though try has a return statement, finally still runs before the function actually returns control to the caller. "finally ran" prints during the function call, before f() actually returns its value; only afterward is result assigned and printed.

    From the quiz Try, Except & Finally

  10. 10.What is the output of the following code?

    javascript
    from functools import reduce
    
    nums = [1, 2, 3]
    result = reduce(lambda acc, x: acc + x, nums, 100)
    print(result)
    • A.6
    • B.TypeError: reduce() takes at most 2 arguments
    • C.100
    • D.106
    Show answer

    Answer: D. 106

    The third argument to reduce(function, iterable, initial) seeds the accumulators **starting value** instead of using the iterables first element. Starting with acc=100: 100+1=101, 101+2=103, 103+3=106.

    From the quiz map, filter & reduce

Mid level Python questions

  1. 11.What is the output of the following code?

    javascript
    import asyncio
    
    async def fails():
        raise ValueError("boom")
    
    async def succeeds():
        return "ok"
    
    async def main():
        results = await asyncio.gather(fails(), succeeds(), return_exceptions=True)
        print(results)
    
    asyncio.run(main())
    • A.Raises ValueError: boom, uncaught, crashing the program.
    • B.[None, 'ok']
    • C.['ok']
    • D.[ValueError('boom'), 'ok']
    Show answer

    Answer: D. [ValueError('boom'), 'ok']

    With return_exceptions=True, gather() does not raise the first exception it encounters — instead, it captures each awaitables outcome (a return value or a raised exception) and returns them all together in a single list, in the original input order. The failing coroutines exception object itself appears in the results list, alongside the successful coroutine's actual return value.

    From the quiz Tasks & Concurrency

  2. 12.What is the output of the following code?

    javascript
    d = {"a": [1]}
    d.setdefault("a", []).append(2)
    d.setdefault("b", []).append(3)
    
    print(d)
    • A.{'a': [1, 2], 'b': [3]}
    • B.{'a': [1, 2]}
    • C.KeyError
    • D.{'a': [1], 'b': [3]}
    Show answer

    Answer: A. {'a': [1, 2], 'b': [3]}

    d.setdefault("a", []): "a" already exists, so setdefault returns the existing list [1] (the [] default is ignored entirely), and .append(2) mutates it to [1, 2]. d.setdefault("b", []): "b" does not exist, so setdefault inserts "b": [] into the dict and returns that same new list, which .append(3) mutates to [3].

    From the quiz Advanced Collections

  3. 13.What is the output of the following code?

    javascript
    from contextlib import suppress
    
    with suppress(FileNotFoundError):
        open("does_not_exist.txt")
    
    print("continued normally")
    • A.TypeError: suppress() missing required argument
    • B.FileNotFoundError: [Errno 2] No such file or directory: 'does_not_exist.txt'
    • C.Nothing is printed — suppress also suppresses the rest of the program.
    • D.continued normally
    Show answer

    Answer: D. continued normally

    contextlib.suppress(ExceptionType) catches and discards any instance of the given exception type(s) raised inside its with block — a concise shortcut for try/except: pass limited to specific exception types. open() on a nonexistent file raises FileNotFoundError, which suppress swallows, so execution continues normally to the print statement.

    From the quiz Chaining & Context Managers

  4. 14.What is the output of the following code?

    javascript
    from itertools import permutations, combinations
    
    items = ['A', 'B', 'C']
    
    print(len(list(permutations(items, 2))))
    print(len(list(combinations(items, 2))))
    • A.3, 3
    • B.6, 6
    • C.6, 3
    • D.3, 6
    Show answer

    Answer: C. 6, 3

    permutations(items, 2) counts order-sensitive selections of 2 from 3 items: AB, BA, AC, CA, BC, CB — 6 total. combinations(items, 2) counts order-insensitive selections (AB and BA count as the same selection): AB, AC, BC — 3 total.

    From the quiz itertools & partial

  5. 15.What is the output of the following code?

    javascript
    x = 100
    squares = [x * x for x in range(5)]
    print(x)
    print(squares)
    • A.100, [0, 1, 4, 9, 16]
    • B.4, [0, 1, 4, 9, 16]
    • C.4, [0, 1, 2, 3, 4]
    • D.100, [0, 1, 2, 3, 4]
    Show answer

    Answer: A. 100, [0, 1, 4, 9, 16]

    In Python 3, list (and set/dict) comprehensions run in their own implicit scope — the loop variable x inside the comprehension does not leak into (or overwrite) the enclosing scope's x. So the outer x stays 100, while squares correctly holds [0, 1, 4, 9, 16].

    From the quiz Functions & Scope

  6. 16.What is the output of the following code?

    javascript
    from dataclasses import dataclass, field
    
    @dataclass
    class Cart:
        items: list = field(default_factory=list)
    
    c1 = Cart()
    c2 = Cart()
    c1.items.append("apple")
    print(c1.items)
    print(c2.items)
    • A.['apple'] then []
    • B.['apple'] then ['apple']
    • C.[] then []
    • D.ValueError: mutable default <class 'list'> for field items is not allowed
    Show answer

    Answer: A. ['apple'] then []

    field(default_factory=list) calls list() fresh for each instance created, giving every Cart its own independent list — unlike a bare mutable default value shared across instances. So c1 and c2 have separate lists, and mutating c1.items has no effect on c2.items.

    From the quiz Dataclasses & ABCs

  7. 17.What is the output of the following code?

    javascript
    import asyncio
    
    counter = 0
    
    async def increment(lock):
        global counter
        async with lock:
            current = counter
            await asyncio.sleep(0)
            counter = current + 1
    
    async def main():
        lock = asyncio.Lock()
        await asyncio.gather(*(increment(lock) for _ in range(5)))
        print(counter)
    
    asyncio.run(main())
    • A.5
    • B.0
    • C.Possibly less than 5, due to a race condition.
    • D.TypeError: Lock() takes no arguments
    Show answer

    Answer: A. 5

    async with lock: ensures only one coroutine at a time executes the critical section. Even though await asyncio.sleep(0) inside the locked section yields control back to the event loop, the lock prevents any other coroutine from entering the critical section until the current holder releases it — so all 5 increments happen safely, one at a time, correctly summing to 5.

    From the quiz Tasks & Concurrency

  8. 18.What is the output of the following code?

    javascript
    import heapq
    
    nums = [5, 1, 8, 3]
    heapq.heapify(nums)
    heapq.heappush(nums, 0)
    
    print(nums[0])
    print(heapq.heappop(nums))
    print(nums[0])
    • A.0, 0, 1
    • B.1, 1, 3
    • C.0, 1, 3
    • D.1, 0, 1
    Show answer

    Answer: A. 0, 0, 1

    After heapify, nums[0] is guaranteed to be the smallest element — but that's before the push. Once heappush(nums, 0) adds 0, it becomes the new smallest, so the first print(nums[0]) shows 0. heappop(nums) removes and returns the current smallest element, 0. After that removal, the new smallest among the remaining elements is 1, so the final print(nums[0]) shows 1.

    From the quiz Advanced Collections

  9. 19.What is the output of the following code?

    javascript
    class Named:
        def __init__(self, name):
            self.name = name
    
        def __enter__(self):
            print(f"enter {self.name}")
            return self
    
        def __exit__(self, *args):
            print(f"exit {self.name}")
    
    with Named("A"), Named("B"):
        print("inside")
    • A.enter A, enter B, inside, exit A, exit B
    • B.enter A, enter B, inside, exit B, exit A
    • C.enter A, inside, exit A, enter B, exit B
    • D.enter B, enter A, inside, exit A, exit B
    Show answer

    Answer: B. enter A, enter B, inside, exit B, exit A

    Multiple context managers in one with statement are entered left to right (A then B) — same as nesting with A(): with B():. They're exited in reverse order (B then A) — a last acquired, first released pattern.

    From the quiz Chaining & Context Managers

  10. 20.What is the output of the following code?

    javascript
    from itertools import groupby
    
    data = [1, 1, 2, 2, 1, 3]
    grouped = {key: list(group) for key, group in groupby(data)}
    print(grouped)
    • A.KeyError
    • B.{1: [1], 2: [2, 2], 3: [3]}
    • C.{1: [1, 1, 1], 2: [2, 2], 3: [3]}
    • D.{1: [1, 1], 2: [2, 2], 3: [3]}
    Show answer

    Answer: B. {1: [1], 2: [2, 2], 3: [3]}

    groupby(data) (without an explicit key function) groups only consecutive equal elements — even if the same value reappears later non-consecutively, it starts a brand-new group rather than merging with an earlier one. For [1, 1, 2, 2, 1, 3], the consecutive runs are: (1, 1), (2, 2), (1,), (3,) — four separate groups, with key 1 appearing twice. Since the dict comprehension assigns to grouped[key] for each group in order, the later group for key 1 (just [1]) overwrites the earlier one ([1, 1]). Final dict: {1: [1], 2: [2, 2], 3: [3]}.

    From the quiz itertools & partial

Senior level Python questions

  1. 21.Which statement correctly contrasts asyncio.gather() with asyncio.TaskGroup (Python 3.11+) regarding sibling failure behavior?

    • A.gather() (by default) cancels siblings on failure, while TaskGroup does not.
    • B.When one task inside an async with asyncio.TaskGroup() as tg: block raises an exception, TaskGroup automatically cancels all the other still-running tasks in the group before propagating — unlike gather()'s default behavior, which leaves siblings running.
    • C.TaskGroup and gather() behave identically regarding sibling cancellation on failure.
    • D.TaskGroup never propagates exceptions at all — it silently logs them instead.
    Show answer

    Answer: B. When one task inside an async with asyncio.TaskGroup() as tg: block raises an exception, TaskGroup automatically cancels all the other still-running tasks in the group before propagating — unlike gather()'s default behavior, which leaves siblings running.

    asyncio.TaskGroup implements structured concurrency — a failure in any child task automatically cancels all its siblings before the exception propagates, ensuring no orphaned background tasks are left running after an error.

    From the quiz Synchronization Primitives

  2. 22.What is the output of the following code?

    javascript
    data = [("apple", 2), ("banana", 1), ("cherry", 2), ("date", 1)]
    
    step1 = sorted(data, key=lambda x: x[0])
    step2 = sorted(step1, key=lambda x: x[1])
    
    print(step2)
    • A.[('apple', 2), ('banana', 1), ('cherry', 2), ('date', 1)]
    • B.Unpredictable/undefined order among tied elements
    • C.[('banana', 1), ('date', 1), ('apple', 2), ('cherry', 2)]
    • D.[('date', 1), ('banana', 1), ('cherry', 2), ('apple', 2)]
    Show answer

    Answer: C. [('banana', 1), ('date', 1), ('apple', 2), ('cherry', 2)]

    Python's sorted() is stable — it preserves the relative order of elements that compare equal under the current key. step1 sorts alphabetically: apple, banana, cherry, date. step2 then sorts by the number, but because the sort is stable, elements sharing the same number retain their relative order from step1. Among the 1s: banana preceded date in step1, so that order carries through. Among the 2s: apple preceded cherry in step1, likewise preserved. Final: banana(1), date(1), apple(2), cherry(2).

    From the quiz Ordering & Aliasing

  3. 23.What is the output of the following code?

    javascript
    class Broken:
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_value, traceback):
            raise ValueError("cleanup failed")
    
    try:
        with Broken():
            raise RuntimeError("original error")
    except Exception as e:
        print(type(e).__name__)
    • A.ValueError — the exception raised inside __exit__ replaces the original exception from the with block's body.
    • B.Both exceptions somehow propagate together, and type(e).__name__ raises an error trying to determine which one to report.
    • C.RuntimeError — the original exception always takes priority over anything __exit__ does.
    • D.TypeError: __exit__() cannot raise exceptions
    Show answer

    Answer: A. ValueError — the exception raised inside __exit__ replaces the original exception from the with block's body.

    If __exit__ itself raises a new exception, that new exception replaces whatever exception was already propagating from the with block's body. The original RuntimeError is discarded as the active exception (though still linked via __context__). The code catching the exception sees ValueError, not RuntimeError.

    From the quiz ExitStack & Exception Groups

  4. 24.What is the output of the following code?

    javascript
    def memoize(func, cache={}):
        def wrapper(n):
            if n not in cache:
                cache[n] = func(n)
            return cache[n]
        return wrapper
    
    call_count = 0
    
    def slow_square(n):
        global call_count
        call_count += 1
        return n * n
    
    fast_square = memoize(slow_square)
    fast_square(5)
    fast_square(5)
    fast_square(3)
    
    print(call_count)
    • A.TypeError: memoize() missing 1 required positional argument: 'cache'
    • B.3
    • C.2
    • D.1
    Show answer

    Answer: C. 2

    fast_square(5) (first call) isn't cached yet, so slow_square runs, incrementing call_count to 1. fast_square(5) (second call) finds 5 already cached — no new call. fast_square(3) is a genuinely new argument, so slow_square runs again, incrementing call_count to 2.

    From the quiz Composition & Closures

  5. 25.What is the output of the following code?

    javascript
    class Point:
        __slots__ = ('x', 'y')
    
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
    p = Point(1, 2)
    p.x = 10
    print(p.x)
    p.z = 5
    • A.1, then AttributeError
    • B.10, then 5 is stored without issue
    • C.10, then AttributeError
    • D.10, then TypeError
    Show answer

    Answer: C. 10, then AttributeError

    __slots__ restricts instances to exactly the named attributes (x, y) and prevents a per-instance __dict__ from being created. Reassigning p.x = 10 is valid since x is a declared slot, so print(p.x) outputs 10. But p.z = 5 tries to set an attribute not declared in __slots__, raising AttributeError.

    From the quiz Advanced Patterns

  6. 26.What is the output of the following code?

    javascript
    class Stack:
        def __class_getitem__(cls, item):
            return f"{cls.__name__}[{item.__name__}]"
    
    print(Stack[int])
    • A.TypeError: 'type' object is not subscriptable
    • B.<class '__main__.Stack'>
    • C.Stack[int]
    • D.AttributeError
    Show answer

    Answer: C. Stack[int]

    __class_getitem__ (PEP 560) lets a class support subscript syntax (Stack[int]) at the class level, not just on instances. Python calls Stack.__class_getitem__(int), which returns the formatted string "Stack[int]", and print displays it.

    From the quiz Advanced OOP

  7. 27.Which statement correctly describes safely interacting with an asyncio event loop from a different OS thread?

    • A.asyncio objects (Futures, Tasks, Queues) are fully thread-safe by default — any thread can freely call their methods directly.
    • B.threading.Lock should always be used instead of asyncio.Lock when working with asyncio code, since asyncio doesn't have real concurrency anyway.
    • C.It's impossible for any other thread to ever communicate with a running asyncio event loop.
    • D.Calling asyncio objects (like setting a Future's result, or scheduling a callback) directly from a different OS thread is not safe — you must use thread-safe methods like loop.call_soon_threadsafe() to schedule that interaction properly onto the event loop's own thread.
    Show answer

    Answer: D. Calling asyncio objects (like setting a Future's result, or scheduling a callback) directly from a different OS thread is not safe — you must use thread-safe methods like loop.call_soon_threadsafe() to schedule that interaction properly onto the event loop's own thread.

    asyncios core objects are designed to be used from within the event loops own thread — calling their methods directly from a different thread is unsafe. loop.call_soon_threadsafe(callback) is the correct, safe bridge for scheduling cross-thread interactions.

    From the quiz Synchronization Primitives

  8. 28.What is the output of the following code?

    javascript
    class Fruit:
        def __init__(self, name):
            self.name = name
    
        def __eq__(self, other):
            return isinstance(other, Fruit) and self.name == other.name
    
    basket = [Fruit("apple"), Fruit("banana")]
    print(Fruit("apple") in basket)
    print(Fruit("apple") is basket[0])
    • A.True, True
    • B.TypeError: Fruit.__eq__ not supported by 'in'
    • C.True, False
    • D.False, False
    Show answer

    Answer: C. True, False

    The in operator checks membership using == (calling __eq__) against each element, not is. Fruit("apple") == basket[0] is True, since __eq__ compares by .name, even though they're distinct object instances — so in finds a match, True. Fruit("apple") is basket[0] checks identity, and these are genuinely two separate objects (a freshly constructed one vs. the one stored in the list), so is is False.

    From the quiz Ordering & Aliasing

  9. 29.What is the output of the following code?

    javascript
    from contextlib import ExitStack
    
    def cleanup(name):
        print(f"cleaning up {name}")
    
    with ExitStack() as stack:
        stack.callback(cleanup, "resource-1")
        stack.callback(cleanup, "resource-2")
        print("working")
    • A.working, cleaning up resource-2, cleaning up resource-1
    • B.cleaning up resource-1, cleaning up resource-2, working
    • C.TypeError: callback() missing required argument
    • D.working, cleaning up resource-1, cleaning up resource-2
    Show answer

    Answer: A. working, cleaning up resource-2, cleaning up resource-1

    stack.callback(func, *args) registers a plain function to be called automatically when the ExitStacks `with` block exits. Multiple registered callbacks run in reverse registration order (LIFO), exactly like `enter_context()`-registered context managers — so `"resource-2"`s cleanup runs before "resource-1"'s.

    From the quiz ExitStack & Exception Groups

  10. 30.What is the output of the following code?

    javascript
    def bold(func):
        def wrapper():
            return f"<b>{func()}</b>"
        return wrapper
    
    def italic(func):
        def wrapper():
            return f"<i>{func()}</i>"
        return wrapper
    
    @bold
    @italic
    def greet():
        return "hello"
    
    print(greet())
    • A.<b>hello</b><i>hello</i>
    • B.<i><b>hello</b></i>
    • C.<b><i>hello</i></b>
    • D.TypeError: greet() takes 0 positional arguments
    Show answer

    Answer: C. <b><i>hello</i></b>

    Decorators apply bottom-up (closest to the function first): @italic wraps greet first, producing an italic-wrapping function; then @bold wraps that result. Calling greet() (now bold's wrapper) computes the italic-wrapped inner result (<i>hello</i>) first, then wraps that in bold tags: <b><i>hello</i></b>.

    From the quiz Composition & Closures

Practise 650+ more Python questions

Timed quizzes with instant feedback, the questions you miss coming back until they stick, coding challenges and AI mock interviews — free to start.