pencils ready ✎

Python MCQs multiple-choice questions with answers & explanations

All 22 Python quiz questions on one page. Pick an answer in your head, then open Show answer to check it and read why. Want a score and a timer? Take them as a quiz instead.

  1. 1.

    What does this print?

    easy
    def append_to(x, items=[]):
        items.append(x)
        return items
    
    append_to(1)
    print(append_to(2))
    1. A[2]
    2. B[2, 1]
    3. C[1, 2]
    4. DTypeError
    Show answer

    Answer: C ([1, 2])

    The default list is created once, when def runs, and is shared by every call that omits items. The first call leaves 1 in it, so the second call returns [1, 2]. The fix is items=None and creating the list inside the function.

  2. 2.

    What does this print?

    mid
    funcs = [lambda: i * 10 for i in range(3)]
    print([f() for f in funcs])
    1. A[0, 10, 20]
    2. B[0, 0, 0]
    3. CNameError
    4. D[20, 20, 20]
    Show answer

    Answer: D ([20, 20, 20])

    Closures capture the variable i, not its value at creation time, and look it up when called. By then the loop has finished with i == 2, so every lambda returns 20. Binding the value with a default argument, lambda i=i: i * 10, gives [0, 10, 20].

  3. 3.

    What happens when this runs?

    mid
    count = 0
    
    def increment():
        count += 1
        return count
    
    print(increment())
    1. AIt prints 1
    2. BIt prints 0
    3. CIt raises UnboundLocalError
    4. DIt raises SyntaxError at definition
    Show answer

    Answer: C (It raises UnboundLocalError)

    count += 1 is an assignment, so the compiler treats count as local to the whole function. Reading it before it has a local value raises UnboundLocalError. Declaring global count inside the function (or better, passing and returning the value) fixes it.

  4. 4.

    What does this print?

    easy
    def f(*args, **kwargs):
        print(type(args).__name__, type(kwargs).__name__)
    
    f(1, a=2)
    1. Alist dict
    2. Btuple dict
    3. Ctuple list
    4. Dlist list
    Show answer

    Answer: B (tuple dict)

    *args always collects extra positional arguments into a tuple, and **kwargs collects extra keyword arguments into a dict. Neither is ever a list.

  5. 5.

    What does this print?

    hard
    data = [3, -1, 4]
    kept = [y for x in data if (y := x * 2) > 0]
    print(kept, y)
    1. A[6, 8] 8
    2. B[6, 8] -2
    3. C[6, 8] 4
    4. DNameError
    Show answer

    Answer: A ([6, 8] 8)

    An assignment expression inside a comprehension binds its name in the enclosing scope, so y exists afterwards. It is reassigned on every iteration, and the last item 4 gives y = 8. The loop variable x itself does not leak.

  6. 6.

    What does this print?

    mid
    grid = [[0] * 3] * 3
    grid[0][0] = 1
    print(grid)
    1. A[[1, 0, 0], [1, 0, 0], [1, 0, 0]]
    2. B[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
    3. C[[1, 1, 1], [0, 0, 0], [0, 0, 0]]
    4. D[[1, 1, 1], [1, 1, 1], [1, 1, 1]]
    Show answer

    Answer: A ([[1, 0, 0], [1, 0, 0], [1, 0, 0]])

    Multiplying the outer list copies the reference to one inner list three times, so all three rows are the same object and the change shows up in each. Build independent rows with [[0] * 3 for _ in range(3)].

  7. 7.

    What does this print?

    easy
    print(type((1)), type((1,)))
    1. A<class 'tuple'> <class 'tuple'>
    2. B<class 'int'> <class 'int'>
    3. C<class 'int'> <class 'tuple'>
    4. D<class 'tuple'> <class 'int'>
    Show answer

    Answer: C (<class 'int'> <class 'tuple'>)

    Parentheses alone only group an expression, so (1) is just the int 1. It is the comma that makes a tuple: (1,) or even 1, is a one-element tuple.

  8. 8.

    What does this print?

    hard
    d = {True: "a", 1: "b", 1.0: "c"}
    print(d)
    1. A{True: 'a', 1: 'b', 1.0: 'c'}
    2. B{1.0: 'c'}
    3. C{True: 'a'}
    4. D{True: 'c'}
    Show answer

    Answer: D ({True: 'c'})

    True == 1 == 1.0 and they hash the same, so they are the same dict key. The first key object inserted (True) is kept, while each later assignment overwrites the value, leaving {True: 'c'}.

  9. 9.

    What does this print?

    mid
    import copy
    
    a = {"tags": ["x"], "n": 1}
    b = copy.copy(a)
    b["tags"].append("y")
    b["n"] = 2
    print(a)
    1. A{'tags': ['x', 'y'], 'n': 1}
    2. B{'tags': ['x'], 'n': 1}
    3. C{'tags': ['x', 'y'], 'n': 2}
    4. D{'tags': ['x'], 'n': 2}
    Show answer

    Answer: A ({'tags': ['x', 'y'], 'n': 1})

    A shallow copy creates a new dict whose values are the same objects. Appending mutates the shared list, so a sees 'y', but b["n"] = 2 only rebinds a key in b. Use copy.deepcopy to make the nested list independent.

  10. 10.

    What does this print?

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

    Answer: D ([1, 2, 3] True)

    On a list, += extends the existing object in place, so b sees 3 and c is that same list. a = a + [4] builds a new list and rebinds only a, so b and c still point to [1, 2, 3].

  11. 11.

    What does this print?

    easy
    squares = (n * n for n in range(3))
    print(sum(squares), sum(squares))
    1. A5 5
    2. B5 0
    3. C0 5
    4. DTypeError
    Show answer

    Answer: B (5 0)

    A generator expression is a single-pass iterator. The first sum consumes 0 + 1 + 4 = 5, and the second finds it already exhausted, so it sums nothing and returns 0. Use a list if you need to iterate more than once.

  12. 12.

    What does this print?

    easy
    first, *middle, last = "abcd"
    print(middle)
    1. A('b', 'c')
    2. B'bc'
    3. C['b', 'c']
    4. D['a', 'b', 'c']
    Show answer

    Answer: C (['b', 'c'])

    A starred target in an assignment always collects the leftover items into a list, whatever the type of the iterable being unpacked. Here first is 'a', last is 'd', and middle is ['b', 'c'].

  13. 13.

    What does this print?

    easy
    s = "python"
    print(s[::-2])
    1. A'pto'
    2. B'yhn'
    3. C'noht'
    4. D'nhy'
    Show answer

    Answer: D ('nhy')

    With a negative step and no bounds, slicing starts from the last character and walks backwards: n, skip o, h, skip t, y, skip p. So the result is 'nhy'; 'pto' would be s[::2].

  14. 14.

    What does this print?

    mid
    words = ["bb", "a", "ccc", "dd", "e"]
    print(sorted(words, key=len))
    1. A['a', 'e', 'bb', 'dd', 'ccc']
    2. B['e', 'a', 'dd', 'bb', 'ccc']
    3. C['a', 'e', 'dd', 'bb', 'ccc']
    4. D['ccc', 'bb', 'dd', 'a', 'e']
    Show answer

    Answer: A (['a', 'e', 'bb', 'dd', 'ccc'])

    Python's sort is stable: items with equal keys keep their original relative order. 'a' came before 'e' and 'bb' before 'dd' in the input, so they stay that way within each length group.

  15. 15.

    What does this print?

    easy
    try:
        x = int("42")
    except ValueError:
        print("bad", end=" ")
    else:
        print("ok", end=" ")
    finally:
        print("done")
    1. Abad done
    2. Bok
    3. Cbad ok done
    4. Dok done
    Show answer

    Answer: D (ok done)

    int("42") succeeds, so the except block is skipped and the else block runs, because else runs only when the try raised nothing. finally runs in every case, giving ok done.

  16. 16.

    What does this print?

    mid
    def f():
        try:
            return "try"
        finally:
            print("finally", end=" ")
    
    print(f())
    1. Atry
    2. Bfinally try
    3. Ctry finally
    4. Dfinally
    Show answer

    Answer: B (finally try)

    The return value is computed, then the finally block runs before the function actually returns, so finally is printed first. The caller then prints the returned try. (A return inside finally would override it, and 3.14 warns about that.)

  17. 17.

    What does this print?

    hard
    class A:
        def who(self): return "A"
    class B(A):
        def who(self): return "B" + super().who()
    class C(A):
        def who(self): return "C" + super().who()
    class D(B, C):
        def who(self): return "D" + super().who()
    
    print(D().who())
    1. ADBA
    2. BDCBA
    3. CDBACA
    4. DDBCA
    Show answer

    Answer: D (DBCA)

    D's MRO is D, B, C, A, object. super() means the next class in the instance's MRO, so B's super().who() calls C.who, not A.who, and A runs only once: DBCA.

  18. 18.

    What happens when the last line runs?

    hard
    class Point:
        def __init__(self, x):
            self.x = x
        def __eq__(self, other):
            return self.x == other.x
    
    print({Point(1)})
    1. AA set containing one Point
    2. BA TypeError: unhashable type
    3. CAn empty set() is printed
    4. DAn AttributeError on other.x
    Show answer

    Answer: B (A TypeError: unhashable type)

    Defining __eq__ without __hash__ sets __hash__ to None, making instances unhashable, so they cannot go into a set or be dict keys. Python does this to protect the rule that equal objects must have equal hashes. Define __hash__ over the same fields to fix it.

  19. 19.

    What does this print?

    mid
    class Animal:
        @classmethod
        def create(cls):
            return cls()
    
    class Dog(Animal):
        pass
    
    print(type(Dog.create()).__name__)
    1. AAnimal
    2. Btype
    3. CDog
    4. DTypeError
    Show answer

    Answer: C (Dog)

    A classmethod receives the class it was called on as cls. Called via Dog, cls is Dog, so cls() builds a Dog. That is why classmethods are the idiomatic way to write alternative constructors that work for subclasses.

  20. 20.

    What happens when this code runs?

    mid
    from dataclasses import dataclass
    
    @dataclass
    class Cart:
        items: list = []
    1. AIt works; all carts share one list
    2. BClass definition raises ValueError
    3. CIt raises TypeError on first Cart()
    4. DIt works; each cart gets a fresh list
    Show answer

    Answer: B (Class definition raises ValueError)

    @dataclass rejects mutable defaults like list, dict and set with a ValueError as soon as the class is defined, precisely to prevent the shared-default bug. Use items: list = field(default_factory=list).

  21. 21.

    What does this print?

    hard
    RED = "red"
    
    def check(color):
        match color:
            case RED:
                return "red!"
    
    print(check("blue"))
    1. Ared!
    2. BNone
    3. CSyntaxError
    4. DNameError
    Show answer

    Answer: A (red!)

    A bare name in a case is a capture pattern: it matches anything and binds the subject to RED, so "blue" matches. It is not compared to the constant; use a dotted name such as Color.RED for that. (If another case followed, Python would reject it as unreachable.)

  22. 22.

    On standard CPython (with the GIL), which option actually runs a CPU-bound pure-Python function in parallel across 8 cores?

    mid
    1. AThreadPoolExecutor with 8 workers
    2. BProcessPoolExecutor with 8 workers
    3. Casyncio.gather over 8 coroutines
    4. DWrapping the function in @lru_cache
    Show answer

    Answer: B (ProcessPoolExecutor with 8 workers)

    Separate processes each have their own interpreter and GIL, so they run truly in parallel. Threads take turns holding the GIL for pure-Python bytecode, asyncio is single-threaded, and caching only helps repeated identical calls.

esc