Level 3 — ProductionIntroduction to Programming (Python)

Introduction to Programming (Python)

45 minutes60 marksprintable — key stays hidden on paper

Difficulty: Level 3 — Production (from-scratch derivations, code-from-memory, explain-out-loud) Time limit: 45 minutes Total marks: 60

Write all code from memory. Where a question asks you to explain, write in complete prose as if teaching aloud. Trace exactly what Python does — no hand-waving.


Question 1 — Data types, conversion & operators (10 marks)

Without running Python, predict the exact output (or error) of each expression, and give a one-line reason for each.

(a) print(7 // 2, -7 // 2, 7 % -2) (3) (b) print(type(int("3") + float("2.5"))) (2) (c) print(3 & 5, 3 | 5, 3 ^ 5, 5 << 2) (3) (d) print(bool("") or bool("0"), "" and 5) (2)


Question 2 — Strings from memory (10 marks)

Given s = " Data-Science-101 ":

(a) Write one line using string methods that produces the list ['Data', 'Science', '101'] (strip surrounding spaces, split on -). (3) (b) Predict the output of print(s.strip()[5:12]). (2) (c) Predict the output of print("ab" * 3 + "c"[::-1]). (2) (d) Using an f-string, write one line that prints Pi is 3.14 given pi = 3.14159. (3)


Question 3 — Control flow: write from scratch (12 marks)

Write a complete program (no libraries) that:

  • Reads an integer n from the user via input() (assume valid input).
  • Prints every integer from 1 to n inclusive, but:
    • for multiples of 3 print Fizz instead of the number,
    • for multiples of 5 print Buzz,
    • for multiples of both print FizzBuzz.

(a) Give the full working code. (8) (b) Explain out loud why input() needs conversion here and what happens if you forget it. (2) (c) Explain why the order of your if/elif conditions matters. (2)


Question 4 — Data structures & comprehensions (12 marks)

You are given:

records = [
    {"name": "Ana", "score": 88},
    {"name": "Ben", "score": 72},
    {"name": "Cara", "score": 95},
    {"name": "Ben", "score": 60},
]

(a) Using a list comprehension, build a list of names of everyone scoring ≥ 80. (3) (b) Using a dict comprehension, build {name: score} — explain what happens to the duplicate key "Ben". (3) (c) Write one line using sorted() with a lambda key to sort records by score descending. (3) (d) Build a set of unique names from records and state its length. (3)


Question 5 — Functions, scope & flexible arguments (10 marks)

(a) Write a function stats(*nums, **opts) that returns the sum of nums, but if opts contains average=True it returns the mean instead. Include a one-line docstring. (5) (b) Explain the LEGB rule and trace which value is printed:

x = "global"
def outer():
    x = "enclosing"
    def inner():
        print(x)
    inner()
outer()

(3) (c) Explain what nonlocal would change if added inside inner() with x = "changed". (2)


Question 6 — Recursion from scratch (6 marks)

(a) Write a recursive function fib(n) returning the n-th Fibonacci number (fib(0)=0, fib(1)=1). Mark the base case and recursive case. (4) (b) Explain out loud one reason this naive recursion is inefficient and one situation where deep recursion causes a RecursionError. (2)


Answer keyMark scheme & solutions

Question 1 (10)

(a) 3 -4 -1 — (3)

  • 7 // 2 = 3 (floor of 3.5). (1)
  • -7 // 2 = -4 (floor division rounds toward −∞, floor of −3.5 is −4). (1)
  • 7 % -2 = -1 (result takes sign of divisor: 7 = (-2)(-4) + (-1)). (1)

(b) <class 'float'>int("3")=3, float("2.5")=2.5, int+float promotes to float. (2)

(c) 1 7 6 20 — (3)

  • 3 & 5: 011 & 101 = 001 = 1. (0.75)
  • 3 | 5: 011 | 101 = 111 = 7. (0.75)
  • 3 ^ 5: 011 ^ 101 = 110 = 6. (0.75)
  • 5 << 2: 5 * 4 = 20. (0.75)

(d) True (space) `` (empty) — (2)

  • bool("")=False, bool("0")=True (non-empty string is truthy) → False or True = True. (1)
  • "" and 5: short-circuit — "" is falsy so and returns "" (prints nothing). (1)

Question 2 (10)

(a) s.strip().split("-")['Data', 'Science', '101']. (3) (strip 1, split on - 2)

(b) s.strip() = "Data-Science-101"; index [5:12] = "Science". (2) (Indices: 0–3 Data, 4 -, 5–11 Science.)

(c) "ababab" + "c" = "abababc". Reversing a single char "c"[::-1] = "c". (2)

(d) print(f"Pi is {pi:.2f}")Pi is 3.14. (3) (f-string 1, embed expr 1, .2f format 1)


Question 3 (12)

(a) (8) — full code:

n = int(input())
for i in range(1, n + 1):
    if i % 15 == 0:
        print("FizzBuzz")
    elif i % 3 == 0:
        print("Fizz")
    elif i % 5 == 0:
        print("Buzz")
    else:
        print(i)

Marks: int(input()) (2), correct range(1, n+1) inclusive (2), FizzBuzz first / mod-15 check (2), Fizz & Buzz & else branches (2).

(b) input() always returns a str; range() needs an int. Without int() you'd get TypeError (str is not an integer) at range. (2)

(c) The combined multiple (15) must be tested first; otherwise a number like 15 matches i % 3 == 0 and prints only Fizz, never reaching the FizzBuzz case. (2)


Question 4 (12)

(a) [r["name"] for r in records if r["score"] >= 80]['Ana', 'Cara']. (3)

(b) {r["name"]: r["score"] for r in records}{'Ana': 88, 'Ben': 60, 'Cara': 95}. The duplicate key "Ben" is overwritten by the later entry (60), since dict keys are unique — last value wins. (3) (code 2, explanation 1)

(c) sorted(records, key=lambda r: r["score"], reverse=True). (3)

(d) {r["name"] for r in records} = {'Ana', 'Ben', 'Cara'}, length 3 (Ben deduplicated). (3) (code 2, length 1)


Question 5 (10)

(a) (5)

def stats(*nums, **opts):
    """Return sum of nums, or mean if average=True."""
    total = sum(nums)
    if opts.get("average"):
        return total / len(nums)
    return total

Marks: *nums/**kwargs signature (2), docstring (1), sum default (1), opts.get("average") branch returning mean (1).

(b) LEGB = lookup order Local → Enclosing → Global → Built-in. In inner(), x is not local, so Python looks in the enclosing outer scope and finds "enclosing" → prints enclosing. (3) (rule 2, correct answer 1)

(c) Adding nonlocal x then x = "changed" inside inner() rebinds the enclosing x, so outer's x becomes "changed" (rather than creating a new local). (2)


Question 6 (6)

(a) (4)

def fib(n):
    if n < 2:          # base case: fib(0)=0, fib(1)=1
        return n
    return fib(n-1) + fib(n-2)   # recursive case

Marks: base case correct (2), recursive case correct (2).

(b) Inefficient: it recomputes the same subproblems exponentially (O(2^n) calls, no memoization). Deep recursion (e.g. large n without a loop/memo, or an unbounded recursion) exceeds Python's default recursion limit (~1000) and raises RecursionError. (2)


[
  {"claim":"7//2=3, -7//2=-4, 7%-2=-1", "code":"result = (7//2==3) and (-7//2==-4) and (7 % -2 == -1)"},
  {"claim":"bitwise: 3&5=1,3|5=7,3^5=6,5<<2=20", "code":"result = (3&5==1) and (3|5==7) and (3^5==6) and (5<<2==20)"},
  {"claim":"dict comp overwrites duplicate Ben with 60, set of names has length 3", "code":"records=[{'name':'Ana','score':88},{'name':'Ben','score':72},{'name':'Cara','score':95},{'name':'Ben','score':60}]; d={r['name']:r['score'] for r in records}; result = (d['Ben']==60) and (len({r['name'] for r in records})==3)"},
  {"claim":"names scoring >=80 are Ana and Cara; fib(10)=55", "code":"records=[{'name':'Ana','score':88},{'name':'Ben','score':72},{'name':'Cara','score':95},{'name':'Ben','score':60}]; names=[r['name'] for r in records if r['score']>=80]; \ndef fib(n):\n    return n if n<2 else fib(n-1)+fib(n-2)\nresult = (names==['Ana','Cara']) and (fib(10)==55)"}
]