Final Exam — Study Guide
Coverage — what the final covers
The final is cumulative: its questions reach all eight course objectives, from reading and running your first program in Week 1 to classes and objects in Week 15. It is given in Week 16, the finals week, which has no new material. The exam has 40 questions, and every question counts the same. Thirty-one are multiple choice with one best answer, two ask you to select every choice that fits, and seven are true/false statements. Fourteen questions are on the easier side, 20 are of medium difficulty, and 6 are harder. There is no time limit, and you get one attempt. It opens in Week 16, in Canvas, when your instructor sets it to open.
The questions were drawn from the course's question bank, and every objective appears at least once. The draw leans hard on the second half of the term. Here is what this form actually contains:
| Week | Topic | Questions | Objectives assessed |
|---|---|---|---|
| 6 | Loops II: for, range, and nested loops | 1 | Objective 4 (1) |
| 9 | Lists | 6 | Objective 6 (6) |
| 10 | Tuples, dictionaries, and sets | 5 | Objective 6 (4), Objective 3 (1) |
| 11 | Strings in depth and text processing | 4 | Objective 7 (2), Objective 2 (2) |
| 12 | File Input/Output and exceptions | 8 | Objective 7 (6), Objective 1 (2) |
| 13 | Algorithms: searching, sorting, and growth | 5 | Objective 8 (5) |
| 14 | Recursion | 5 | Objective 8 (4), Objective 5 (1) |
| 15 | Introduction to object-oriented programming | 6 | Objective 8 (5), Objective 5 (1) |
| Objective | What you do | Weeks that teach it | Questions on this exam |
|---|---|---|---|
| Objective 1 | Explain how a program runs, trace it, and read an error message | 1 | 2 |
| Objective 2 | Use the core types, evaluate expressions, and index, slice, and format strings | 2, 3 | 2 |
| Objective 3 | Write Boolean expressions and control which code runs with if, elif, and else | 4 | 1 |
| Objective 4 | Write while and for loops with counters, accumulators, nested loops, and break | 5, 6 | 1 |
| Objective 5 | Define and call functions, tell return from print, and reason about scope | 7 | 2 |
| Objective 6 | Use lists, tuples, dictionaries, and sets, and explain aliasing versus copying | 9, 10 | 10 |
| Objective 7 | Process text with string methods, read and write files, and handle exceptions by type | 11, 12 | 8 |
| Objective 8 | Trace searches and selection sort, name growth classes, trace recursion, and define a class | 13, 14, 15 | 14 |
Read the two tables together. Thirty-nine of the 40 questions come from the material of Weeks 9–15, and one comes from Week 6. Each question assesses exactly one objective. Objective 8 carries 14 questions, the most of any objective, spread across searching and sorting, recursion, and classes. Collections (Objective 6) carry 10, and text, files, and exceptions (Objective 7) carry 8. Week 12 alone carries 8 questions, more than any other week, so put most of your review on Weeks 9–15 and give Week 12 extra time.
The first-half objectives still count. Eight questions assess Objective 1 through Objective 5. One sits in Week 6, on how a nested loop runs. The other seven sit inside later material, where the early skills come back: a condition that guards a dictionary lookup in Week 10, indexing and slicing a string in Week 11, reading what a failing program prints and the type its traceback names in Week 12, and the function ideas of parameters, arguments, and what a call hands back inside recursion in Week 14 and classes in Week 15. The two habits from the first half, predict then run and read the error from the bottom up, run through almost every question.
Topic-by-topic review
Each unit gives the idea in plain words first, then the course's exact definitions, then the mistakes students make most often and the cure for each. The "deeper review" line tells you where to look in that week's materials. Units 6–12 carry the weight of the exam, so they get the most space. Every program output quoted below is what Python 3 printed when the program ran.
Unit 1 — Programs, tracing, and errors (Weeks 1 and 8)
- Key idea. A recipe card works only if every step is spelled out in order. A computer is the most literal cook there is: it does exactly what you wrote, not what you meant.
- Definitions. An algorithm is a finite, unambiguous list of steps that solves a whole class of problems, such as finding the larger of any two numbers rather than of one particular pair. A program is an algorithm written in a language a computer can run; the computer carries out its instructions literally and in order. To trace is to read a program one line at a time and write down what each step does, so you can predict its output before you run it.
- Reading errors. An error message is the report Python prints when a program cannot go on; you read it from the bottom up, because its last line names the error type. A SyntaxError is the error type Python reports when the parser refuses a program's text, so no line of that program runs at all. A NameError is the error type Python reports when a running program uses a name Python does not know. A runtime exception prints everything before the failing line, then stops; a logic error runs to the end and prints the wrong thing.
- The Week 8 habit. The classic-bug checklist is six slips a reader looks for before running a program: = written where == belongs, a range or slice whose stop is left out, / giving a float, two strings joined by + instead of added, a function with no return handing back None, and a while loop whose body never moves its condition toward False.
- Predictable mistakes → cures. Treating a red error as a verdict: it is a map, so read the last line first. Expecting a program to print the lines above a SyntaxError: nothing runs, because the parser refused the whole text. Quoting the words after the colon: the course keys on the type alone.
- Deeper review: the Week 1 chapter and the Week 8 review chapter.
Unit 2 — Values, types, and strings (Weeks 2 and 3)
- Key idea. A label on a jar holds whatever you last put in it. A variable is that label, and the type of what is inside decides what you can do with it.
- Definitions. A variable is a name that holds a value so a program can use that value later. Assignment puts the value on the right of = into the name on the left; read = as "gets". True division with
/always gives a float, even when the two numbers divide evenly. Floor division with//gives the quotient rounded down to the next whole number toward negative infinity. The remainder operator%gives what is left over after floor division, carrying the sign of the number you divide by. Operator precedence:**first, then*,/,//, and%, then+and−, left to right within a level, with parentheses always first. - Pinned runs.
2 + 3 * 4is14;10 / 2is5.0;-7 // 2is −4 and-7 % 3is 2;round(2.5)is 2 andround(3.5)is 4, because round-half-to-even picks the even neighbour;"2" + "3"is23;"5" + 3raises TypeError;int(3.9)is 3, because truncation cuts the decimal part off. - Strings.
input()hands back exactly what was typed as a string, always. Zero-based indexing numbers positions from 0, so the last character sits at the length minus 1. A negative index counts back from the end. A slices[start:stop]keeps the characters from index start up to, but not including, index stop; it is half-open, so"PYTHON"[1:4]isYTH. An IndexError comes from an index with no character there, while a slice past the end quietly stops at the end. - Predictable mistakes → cures. Reading
//as rounding: it floors. Including the stop of a slice: count the stop out. Expectinginput()to give a number: convert withint()first. Expecting"7" * 3to be 21: it repeats the text,777. - Deeper review: the Week 2 and Week 3 chapters and slides.
Unit 3 — Booleans and conditionals (Week 4)
- Key idea. A ticket counter asks one question at a time and stops at the first "yes". An if statement does the same.
- Definitions. A Boolean expression is an expression that Python works out to one of the two bool values, True or False. The equality operator
==asks whether two values are equal, unlike=, which gives a name a value. The logical operators areand(True only when both sides are True),or(True when at least one side is True), andnot(flips one value). The priority, said in words, is not, then and, then or, soTrue or False and Falseis True. An elif is checked only when every test above it was False, and only the first true branch runs. A logic error lets the program run without any error message but makes it print the wrong thing. - Truthiness. Falsy values, such as the empty string and the number 0, act as False in a condition; truthy values, such as any string with at least one character, act as True. So
bool("")is False andbool("0")is True. - Predictable mistakes → cures. Writing
=inside a condition: that is a SyntaxError, so use==. Ordering an elif chain from the loosest test down: the loose test catches everything, so put the strictest test first. Readingandandorleft to right: apply not, then and, then or. - Deeper review: the Week 4 chapter, its truth tables, and the Week 4 lecture tutorial.
Unit 4 — Loops (Weeks 5 and 6)
- Key idea. A swimmer checks the lap board only at the wall, never mid-lap. A while loop tests its condition only at the top of each pass.
- Definitions. A while loop repeats its indented body for as long as its condition is True, checking that condition once at the top of every pass and never in the middle of one. A counter goes up by one on every pass; an accumulator collects a running total. An off-by-one error makes a loop run one pass too many or too few, most often from using less than where less than or equal to was needed. An infinite loop never moves toward the stop. A for loop runs its body once for each item of a sequence.
range()produces whole numbers from a start, 0 when none is given, up to but not including a stop, moving by a step that is 1 when none is given; that exclusive stop meansrange(1, 5)gives 1, 2, 3, and 4. In a nested loop, the whole inner loop runs from start to finish on every pass of the outer loop. - Pinned runs. The counter after
while count <= 5ends at6, one past the last value that passed the test. The reading log's while loop takes 9 days and overshoots to 315 pages.len(range(1, 10, 3))is 3. A 2-by-3 nested loop prints 6 lines. - Predictable mistakes → cures. Expecting the loop to stop mid-body: it checks only at the top. Expecting
range(1, 5)to include 5: the stop is never produced. Thinking the inner loop runs once: it restarts on every outer pass, so multiply the pass counts. - Deeper review: the Week 5 and Week 6 chapters and slides.
Unit 5 — Functions (Week 7)
- Key idea. A vending machine has a screen and a slot. Printing lights up the screen; returning drops something into the slot that the caller can keep.
- Definitions. A function is a named block of code that does one piece of work; it runs only when a line of the program calls it by name. A parameter is a name written inside the parentheses of a def line; when the function is called, it receives the value supplied in the matching position of the call. The value in the call is the argument. return ends a function's call and hands the value written after it back to the line that made the call. None is Python's special value meaning no value at all; a call that reaches the end of its body without a return line hands back None. A local variable is created inside a function and is gone once the call ends; a plain assignment inside a function makes a separate local name instead of changing a global.
- Pinned runs.
pages_left(300, 45)is 255.print(show(4))prints8, thenNone. - Predictable mistakes → cures. Treating print and return as the same: ask "can the caller store it?" Swapping parameter and argument: the parameter lives in the def line, the argument in the call. Expecting a function to change a global by plain assignment: it makes a local.
- Deeper review: the Week 7 chapter and slides, with Python Tutor open beside them.
Unit 6 — Lists (Week 9)
- Key idea. A playlist keeps songs in order under one name, and you can reorder it, add to it, or take a song off. A list is that playlist for any values.
- Definitions. A list is an ordered collection of values kept under one name, written in square brackets with commas between the values, which a program can read by position and change after it is made. A method is a function that belongs to a value and is called with a dot after that value. A change made in place changes the existing list itself, so every name that refers to that list sees it.
- What each method hands back. append adds one item to the end and hands back None. extend adds every item of another list and hands back None. insert places an item at a position and hands back None. pop removes an item, the last one or the one at the position given, and hands that removed item back. remove deletes the first item equal to the value it is given and hands back None. sort rearranges the list in place and hands back None. sorted() builds and hands back a new list in order, leaving the original as it was.
- Aliasing versus copying. An alias is a second name for the very same object, made by an assignment such as
b = a, so a change made through either name shows up through the other. A copy is a separate new list made with a full slice such asa[:]. The operator is gives True only when both names refer to one and the same object;==compares contents. Pinned run: afterb = aandb.append(4),ais[1, 2, 3, 4]anda is bis True; afterb = a[:],astays[1, 2, 3]. - The trap. The mutate-while-iterating trap comes from adding or removing items in a list while a for loop is walking that same list, which makes the loop skip items; walking a copy or building a new list avoids it.
- Pinned runs. The reading log's daily list
[12, 40, 25, 0, 33, 17, 27]totals 154, averages 22.0, and has a biggest day of 40 over 7 days.nums = nums.sort()leavesNone. - Predictable mistakes → cures. Thinking
b = acopies: it makes an alias. Writingnums = nums.sort(): sort hands back None, so call it on its own line or usesorted(). Readingremove(2)as "remove index 2": it removes the value 2. Readingisas==: is asks about identity. - Deeper review: the Week 9 chapter and slides, and the Week 9 Coding Lab.
Unit 7 — Tuples, dictionaries, and sets (Week 10)
- Key idea. A coat check hands you a numbered tag, and the tag finds your coat. A dictionary finds a value by its key, not by its position.
- Tuples. A tuple is an ordered collection written with commas, usually inside parentheses; you read its items by index, but once it is made it cannot be changed. Immutable describes a value that cannot be changed after it is made, such as a tuple or a string; assigning into one of its positions raises TypeError. Unpacking assigns the items of a sequence to several names in one line, and the swap
a, b = b, aworks because the whole right side is worked out first. A one-item tuple needs a trailing comma, as in(5,). - Dictionaries. A dictionary is a collection of key-value pairs written in curly braces, in which you look up, add, or update a value by giving its key. Each key appears once and must be immutable, so a list cannot be a key. A KeyError is raised when a program looks up a missing key with square brackets. .get() hands back the value under a key, or the default given as its second argument when the key is missing, or None when no default is given; it never raises KeyError. The counting idiom is
counts[w] = counts.get(w, 0) + 1. Insertion order is the order keys were first added; looping follows it, a guarantee since Python 3.7. - Sets. A set is an unordered collection that keeps exactly one copy of each value. Use it to drop duplicates and to test membership with
in. Its length is keyed, never its printed order, and{}is an empty dictionary, not an empty set. - Choosing a collection. Look things up by name: dictionary. Unique values: set. A fixed record: tuple. An ordered, changeable sequence: list.
- Predictable mistakes → cures. Expecting a missing key to give None with square brackets: it raises KeyError, so test with
infirst or use.get(). Expectinginon a dictionary to check the values: it checks the keys. Expecting a set to keep your order: it does not. - Deeper review: the Week 10 chapter and slides.
Unit 8 — Strings in depth and text processing (Week 11)
- Key idea. A photocopier never changes the original page. Every string method works the same way: it hands back a new string, and the original is untouched.
- Definitions. A string method is a function that belongs to a string and is called with a dot after it; it hands back a new value and never changes the string itself. The forgot-to-reassign bug is calling a method on a line of its own and expecting the string to change. .strip() removes the whitespace from both ends. .split() cuts a string into pieces and hands back a list of strings; with empty parentheses it cuts on whitespace, and given a separator such as a comma it cuts there instead. .join() is called on a separator and hands back one new string made from a list of strings. .replace() swaps every match of the old text. .find() hands back the position of the first match, counting from 0, or −1 when there is no match; it never hands back True or False. .count() counts the matches that do not overlap. To normalize is to put text into one standard form, such as trimmed and lowercase, before comparing it.
- Pinned runs.
"a,b c".split()is['a,b', 'c']."hello"[::-1]isolleh. Unassigneds.upper()leavesHelloas it was. The reading log's line splits into['day', '3:', '25', 'pages']. - Predictable mistakes → cures. Expecting the original to change: reassign, as in
s = s.upper(). Expectingsplit()to cut on commas: with no argument it cuts on whitespace. Treating.find()as a yes-or-no test: −1 means "not found", and −1 is truthy. Expecting.replace()to change only the first match: it changes every match. - Deeper review: the Week 11 chapter and slides.
Unit 9 — Files and exceptions (Week 12)
This unit carries the most questions of any single week, so give it real time.
- Key idea. A notebook keeps what you wrote after you close it, and a spill does not have to end the meeting if someone is ready with a towel. Files keep data between runs; try and except keep a program running when a named problem happens.
- Files. A file is a named store of text on the computer's disk that keeps its contents after a program stops running. The with statement runs its block with the file under a name and closes the file when the block ends, even when an exception is raised inside it. Write mode
"w"creates the file if it is not there and erases every line of an existing file of the same name before writing. Read mode"r"is the mode Python uses when no mode is given. write() puts one string into the file and adds nothing after it, not even a newline, so a program writing lines adds the newline itself. read() hands back the whole contents as one single string, newlines included. readlines() hands back a list of strings, one for each line, each still ending in its newline. Reading a file always gives strings, so convert withint()before you add. - Exceptions. An exception is the signal Python raises when something goes wrong while a program is running. A traceback is the report it prints; its last line names the exception type. In a try statement, Python runs the try block first; if a line raises, the rest of the try block is skipped, and the except block runs only when the try block raises the exception type that line names. A bare except catches every exception, including the ones caused by typos and other real bugs, and so hides them.
- The error set by type. ZeroDivisionError (dividing or taking a remainder by zero), IndexError, KeyError, TypeError, NameError, ValueError (the right type but an unusable value, such as
int("3.5")), FileNotFoundError (opening a missing file for reading), AttributeError, and SyntaxError. Pinned run: the which-block program printsok 43,not a number, andno file yet. - Predictable mistakes → cures. Thinking
"w"adds to the end: it erases first. Expecting numbers from a file: you get strings, so strip and convert. Thinkingtrycatches everything: it catches the type it names. Expecting the lines after a raise to run: the rest of the try block is skipped. Forgetting what printed before a crash: everything before the failing line still printed. - Deeper review: the Week 12 chapter and slides, and the Week 12 Coding Lab.
Unit 10 — Searching, sorting, and growth (Week 13)
- Key idea. Looking up a word in a printed dictionary, you open near the middle and throw away half the book each time. Checking every page from the front would also work, only far more slowly.
- Definitions. Linear search checks the items one at a time from the front until it finds the target or runs out; it works on any list. Binary search works on a sorted list: it compares the target with the middle item and throws away the half that cannot hold it. Its precondition, something that must already be true, is that the list is sorted. A comparison counter is an accumulator that adds one each time two values are compared, so the work is a count that is the same on every machine. Selection sort, on each pass, finds the smallest item not yet in place and swaps it into the next position. The worst case is the input that makes an algorithm do the most work.
- The course's binary search.
lo = 0,hi = len(items) - 1, the loopwhile lo <= hi:(in words: as long as the low end is less than or equal to the high end), andmid = lo + (hi - lo) // 2. Settinghi = len(items)instead is the classic off-by-one that can index past the end. - Growth classes. Big-O names how the step count grows with the input size n, not the exact count. Constant time, O(1): reading one item by its index. Logarithmic time, O(log n): about one more step each time the list doubles, as in binary search. Linear time, O(n): doubling the list doubles the worst case, as in linear search. Quadratic time, O(n²): selection sort makes n(n − 1) ⁄ 2 comparisons, so doubling the list roughly quadruples the work.
- Pinned runs. On the sorted list
[3, 8, 12, 15, 21, 27, 34, 40], binary search finds 21 at index 4 in 3 comparisons, checking middles 15, 27, then 21. On the list 1 to 16, finding 13 takes 13 comparisons linearly and 4 by halving. Selection sort makes 10, 45, and 190 comparisons on 5, 10, and 20 items. On the unsorted list[27, 8, 40, 3, 15], binary search reports −1 for the 3 that is there. - Predictable mistakes → cures. Running binary search on an unsorted list: sort first. Thinking Big-O is the exact count: it names the growth pattern. Thinking only a clock can measure speed: count comparisons.
- Deeper review: the Week 13 chapter, its instrumented traces, and the Week 13 slides.
Unit 11 — Recursion (Week 14)
- Key idea. Picture a line of people, each asking the person ahead "how many are in front of you?" until the first person says "none". Then the answers travel back down the line, one by one.
- Definitions. Recursion solves a problem by having a function call itself on a smaller copy of the same problem until it is small enough to answer at once. The base case answers without calling the function again; it is what lets the chain of calls stop. The recursive case calls the function again on a smaller input that moves toward the base case. The call stack is the pile of frames for calls that have started and not yet returned; a call adds a frame and a return removes it. Unwinding is the second half, when the deepest call returns first. A RecursionError is raised when the calls go deeper than the depth limit, which usually means the base case is missing or never reached. Iteration solves the same problem with a loop and gives the same answer.
- Pinned runs.
factorial(5)is 120 with 6 calls, for n = 5, 4, 3, 2, 1, and 0, and the returns unwind 1, 1, 2, 6, 24, 120.countdown(3)prints 3, 2, 1, Go!, with the base case last.sum_to(5)is 15 both ways, andsum_to(100)is 5050. - Predictable mistakes → cures. Leaving out the base case: every recursive function needs one, or it raises RecursionError. Calling with the same n: the recursive case must shrink the input. Mixing up the order of output: a print before the recursive call shows on the way down, a print after it shows on the way back up.
- Deeper review: the Week 14 chapter, its call-stack traces, and the Week 14 slides.
Unit 12 — Classes and objects (Week 15)
- Key idea. One cookie cutter makes many cookies, and frosting one cookie does not frost the others. A class is the cutter; each object is its own cookie.
- Definitions. A class is a blueprint for a kind of object, written with the keyword class. An object, also called an instance, is one thing built from a class, with its own attribute values. init is the method Python runs automatically, once, on each new object when the class is called; the program never calls it by name. An attribute is a value stored on an object under a name, such as
self.goal. self is the conventional name of the first parameter of every method, which receives the object the method was called on; Python passes it automatically, so the caller never writes it in the call. State is the current values of all of an object's attributes, and changing one instance leaves every other instance as it was. - The bug. The missing-self bug is assigning to a plain name such as
name = nameinside a method instead of toself.name, so nothing is stored on the object; the next read of that attribute raises AttributeError. - Pinned runs.
ReadingLog(300)after two days prints75 225;ReadingLog(150)after one day prints45 105. Two Counter instances print2 1. - Predictable mistakes → cures. Passing self in the call: Python does it for you. Storing data without
self.: it vanishes when the method ends. Thinking all objects share one attribute: each instance keeps its own. Calling init by hand: calling the class runs it. - Deeper review: the Week 15 chapter and slides.
Representative practice
These are fresh variants, not the exam's questions. Every scenario and program here is illustrative, written for this guide, and every output shown is what Python 3 printed when the program ran. Predict each output on paper before you read it.
Worked example 1 — a community kitchen's trays (Units 1 and 2)
A community kitchen bakes 52 servings and packs 8 servings to a tray.
servings = 52
per_tray = 8
print(servings // per_tray, servings % per_tray)
print(servings / per_tray)
print(2 + servings // per_tray * 2)
print(int("7") + 3, "7" + "3")
print(int("6.5"))
Output
6 4
6.5
14
10 73
ValueError
- Floor division and remainder: 8 × 6 = 48, and 52 − 48 = 4, so 6 full trays and 4 servings left over.
- True division: 52 ÷ 8 = 6.5, a float, because
/always gives one. - Precedence:
//and*share a level and go left to right, so 52 // 8 = 6, then 6 × 2 = 12, then 2 + 12 = 14. - Types:
int("7") + 3adds two numbers to make 10;"7" + "3"joins two strings to make73. - The error:
int("6.5")is the right type, a string, with a valueint()cannot use, so the last line of the traceback names ValueError. Everything above it had already printed.
Why it matters: say the type before you say the value, and read a traceback from its last line.
Worked example 2 — a museum's ticket labels (Unit 3)
A museum labels tickets by age. Here is the first draft, for a visitor aged 70.
age = 70
if age >= 13:
print("standard ticket")
elif age >= 65:
print("senior ticket")
else:
print("child ticket")
Output
standard ticket
The program ran with no error and printed the wrong label: a logic error. The first test, 70 ≥ 13, is True, so its branch runs and the senior test is never checked. Put the strictest test first:
age = 70
if age >= 65:
print("senior ticket")
elif age >= 13:
print("standard ticket")
else:
print("child ticket")
print(bool(0.0), bool("False"))
print(False and True or not False)
Output
senior ticket
False True
True
Now 70 ≥ 65 is True first, so senior ticket prints. The number 0.0 is falsy, but the string "False" has characters, so it is truthy. In the last line, not goes first, so not False is True; then and, so False and True is False; then or, so False or True is True.
Why it matters: only the first true branch runs, and the priority is not, then and, then or.
Worked example 3 — a shuttle's stops and a library's shelves (Unit 4)
A campus shuttle announces its stops with a counter.
stop = 1
while stop <= 4:
print("stop", stop)
stop += 1
print(stop)
Output
stop 1
stop 2
stop 3
stop 4
5
The test runs at the top of each pass. After stop 4 prints, the counter becomes 5, the test 5 ≤ 4 is False, and the loop ends. So the counter finishes at 5, one past the last value that passed.
A library labels shelf slots with a nested loop, where the inner range depends on the outer name.
for shelf in range(1, 4):
for slot in range(shelf):
print(shelf, slot)
Output
1 0
2 0
2 1
3 0
3 1
3 2
The outer loop takes 1, 2, and 3. The inner loop runs to completion each time: 1 pass, then 2, then 3, so 1 + 2 + 3 = 6 lines.
Why it matters: a counter ends one past its last passing value, and the inner loop restarts on every outer pass.
Worked example 4 — a concert's ticket prices (Unit 5)
markup = 2
def ticket_price(base):
markup = 5
return base + markup
def show_price(base):
print(base + markup)
total = ticket_price(10)
print(total)
extra = show_price(10)
print(extra)
print(markup)
Output
15
12
None
2
ticket_price(10)makes a localmarkupof 5 and returns 10 + 5 = 15, which the caller stores and prints.show_price(10)reads the globalmarkup, 2, and prints 10 + 2 = 12 itself.show_pricehas no return line, soextraholds None.- The global
markupis still 2: the plain assignment insideticket_pricemade a separate local name.
Why it matters: print shows a value; return hands it back. A function with no return hands back None.
Worked example 5 — a band's setlist and song lengths (Unit 6)
setlist = ["intro", "ballad", "encore"]
spare = setlist[:]
same = setlist
same.insert(1, "anthem")
dropped = setlist.pop()
print(dropped)
print(setlist)
print(spare)
print(same is setlist, spare == setlist)
Output
encore
['intro', 'anthem', 'ballad']
['intro', 'ballad', 'encore']
True False
spareis a copy made with a full slice;sameis an alias, a second name for the one list.insertthrough the alias changes the shared list to['intro', 'anthem', 'ballad', 'encore'].pop()removes the last item and hands it back, sodroppedisencore.- The copy never changed, so it no longer equals the shared list, and
same is setlistis True because both names refer to one object.
Now the song lengths, in minutes:
minutes = [4, 7, 3, 6]
total = 0
longest = minutes[0]
for m in minutes:
total += m
if m > longest:
longest = m
print(total, total / len(minutes), longest)
in_order = sorted(minutes)
print(in_order, minutes)
print(minutes.sort(), minutes)
Output
20 5.0 7
[3, 4, 6, 7] [4, 7, 3, 6]
None [3, 4, 6, 7]
The total is 4 + 7 + 3 + 6 = 20, the average is 20 ÷ 4 = 5.0 minutes, and the longest song is 7 minutes. sorted() handed back a new list and left minutes alone; .sort() changed minutes in place and handed back None.
Why it matters: an alias shares one list, a slice copy does not, and the changers hand back None while pop hands back an item.
Worked example 6 — a club's mascot vote (Unit 7)
votes = ["owl", "fox", "owl", "heron", "owl", "fox"]
tally = {}
for v in votes:
tally[v] = tally.get(v, 0) + 1
print(tally)
print(tally.get("bear", 0))
print(len(set(votes)))
print("owl" in tally, 3 in tally)
Output
{'owl': 3, 'fox': 2, 'heron': 1}
0
3
True False
- The counting idiom gives each name a starting 0 the first time, then adds one per vote, so owl has 3, fox 2, and heron 1, in the order each first appeared.
.get("bear", 0)hands back the default 0 and raises nothing. With square brackets,tally["bear"]would raise KeyError.- The set keeps one copy of each name, so it holds 3 names.
inchecks keys: "owl" is a key, but 3 is only a value, so the second test is False.
Why it matters: .get() never raises KeyError, and in on a dictionary checks the keys.
Worked example 7 — a ferry survey card (Unit 8)
card = " Great Tour, loved the MAPS "
clean = card.strip().lower()
print(clean)
words = clean.split()
print(len(words), words[0])
print(clean.find("maps"), clean.find("x"))
print(clean.count("o"))
print("-".join(words[:2]))
card.upper()
print(card.strip())
Output
great tour, loved the maps
5 great
22 -1
2
great-tour,
Great Tour, loved the MAPS
.strip()trims both ends, then.lower()makes every letter small: the card is normalized.split()with no argument cuts on whitespace, so there are 5 words, and the comma stays attached totour,.find("maps")hands back the starting index 22;find("x")hands back −1, meaning not found.- The letter o appears in "tour" and "loved", so the count is 2.
joinputs a hyphen between the first two words, comma and all.card.upper()on its own line made a new string and threw it away, so the original card still has its capitals.
Why it matters: string methods hand back new strings, so reassign or nothing changes.
Worked example 8 — a weather station's rain gauges (Unit 9)
A weather station saves four daily rainfall readings, in millimetres, and reads them back.
rain = [12, 0, 7, 5]
with open("rain_mm.txt", "w") as f:
for mm in rain:
f.write(f"{mm}\n")
with open("rain_mm.txt") as f:
text = f.read()
print(type(text))
print(len(text.split()))
wet_days = 0
for piece in text.split():
if int(piece) > 0:
wet_days += 1
print(wet_days)
Output
<class 'str'>
4
3
read() hands back one string, so type reports str. Splitting on whitespace gives 4 pieces, still strings, so int() converts each before the comparison. Three of the four days had rain above 0 millimetres.
Then the station divides 60 minutes among its gauges:
gauges = {"east": 12, "west": 0}
for name in ["east", "west", "north"]:
try:
print(name, 60 // gauges[name])
except ZeroDivisionError:
print(name, "no rain")
except KeyError:
print(name, "no gauge")
Output
east 5
west no rain
north no gauge
East divides cleanly: 60 // 12 = 5. West divides by 0, which raises ZeroDivisionError, so that except block runs. North is not a key, so the lookup raises KeyError and the second except block runs. Each except block runs only for the type it names.
Why it matters: a file gives back strings, and Python runs the except block that names the type actually raised.
Worked example 9 — a trail's elevation markers (Unit 10)
A trail lists 12 marker elevations, in metres, sorted from lowest to highest. This is the course's binary search with one line added to show each middle.
def binary_search(items, target):
lo = 0
hi = len(items) - 1
comparisons = 0
while lo <= hi:
mid = lo + (hi - lo) // 2
comparisons += 1
print("lo", lo, "hi", hi, "mid", mid, "value", items[mid])
if items[mid] == target:
return mid, comparisons
elif items[mid] < target:
lo = mid + 1
else:
hi = mid - 1
return -1, comparisons
heights = [5, 11, 18, 24, 30, 36, 43, 49, 55, 62, 70, 77]
print(binary_search(heights, 62))
print(binary_search(heights, 20))
Output
lo 0 hi 11 mid 5 value 36
lo 6 hi 11 mid 8 value 55
lo 9 hi 11 mid 10 value 70
lo 9 hi 9 mid 9 value 62
(9, 4)
lo 0 hi 11 mid 5 value 36
lo 0 hi 4 mid 2 value 18
lo 3 hi 4 mid 3 value 24
(-1, 3)
- First middle: 0 + (11 − 0) // 2 = 5, which holds 36. Since 36 is less than 62, lo becomes 6.
- Next: 6 + (11 − 6) // 2 = 8, which holds 55, still less, so lo becomes 9.
- Next: 9 + (11 − 9) // 2 = 10, which holds 70, too big, so hi becomes 9.
- Last: index 9 holds 62. Found in 4 comparisons.
- For the absent 20, after 3 comparisons lo is 3 and hi has dropped to 2, so lo passes hi and the search hands back −1.
The course's linear search on the same list, run the same way, reports (9, 10) for 62 and (-1, 12) for 20: 10 and 12 comparisons against 4 and 3.
Why it matters: binary search halves a sorted list, so its count grows by about one each time the list doubles, while linear search's worst case grows with the whole list.
Worked example 10 — a warehouse's crate weights (Unit 10)
A warehouse sorts six crate weights, in kilograms, with the course's selection sort plus a comparison counter.
def selection_sort(items):
items = items[:]
comparisons = 0
for i in range(len(items)):
smallest = i
for j in range(i + 1, len(items)):
comparisons = comparisons + 1
if items[j] < items[smallest]:
smallest = j
items[i], items[smallest] = items[smallest], items[i]
print("pass", i + 1, items)
return comparisons
crates = [31, 9, 24, 4, 17, 12]
print(selection_sort(crates))
print(crates)
Output
pass 1 [4, 9, 24, 31, 17, 12]
pass 2 [4, 9, 24, 31, 17, 12]
pass 3 [4, 9, 12, 31, 17, 24]
pass 4 [4, 9, 12, 17, 31, 24]
pass 5 [4, 9, 12, 17, 24, 31]
pass 6 [4, 9, 12, 17, 24, 31]
15
[31, 9, 24, 4, 17, 12]
- Pass 1 finds the smallest weight, 4, and swaps it to the front.
- Pass 2 finds that 9 is already in place, so it swaps 9 with itself and the list does not change. Pass 6 has one item left, already in place.
- The comparisons are 5 + 4 + 3 + 2 + 1 + 0 = 15, which matches n(n − 1) ⁄ 2 = 6 × 5 ⁄ 2 = 15.
- The original list is unchanged, because the function sorted a copy.
- Doubling to 12 crates gives 12 × 11 ⁄ 2 = 66 comparisons, and 66 ÷ 15 = 4.4, roughly four times the work: quadratic growth.
Why it matters: selection sort's count depends only on the length, and doubling the length roughly quadruples it.
Worked example 11 — a neighbourhood phone tree (Unit 11)
In a phone tree, each person calls three more people. The number of people reached at level 4 is 3 multiplied by itself 4 times.
def power(base, exp):
print("call", exp)
if exp == 0:
return 1
return base * power(base, exp - 1)
print(power(3, 4))
total = 1
for step in range(4):
total = total * 3
print(total)
Output
call 4
call 3
call 2
call 1
call 0
81
81
- The base case is
exp == 0, which returns 1 without another call. - Each recursive case calls with
exp - 1, moving toward the base case: 5 calls in all, for exp = 4, 3, 2, 1, and 0, so 5 frames sit on the call stack at the deepest moment. - Unwinding: 1, then 3 × 1 = 3, then 3 × 3 = 9, then 3 × 9 = 27, then 3 × 27 = 81.
- The loop version gives the same 81: recursion and iteration agree.
Why it matters: every recursive function needs a base case and a recursive case that shrinks toward it.
Worked example 12 — a theater's two ticket booths (Unit 12)
class TicketBooth:
def __init__(self, seats):
self.seats = seats
self.sold = 0
def sell(self, n):
if self.sold + n > self.seats:
return False
self.sold += n
return True
def left(self):
return self.seats - self.sold
east = TicketBooth(40)
west = TicketBooth(25)
print(east.sell(30), west.sell(30))
print(east.sell(15), east.left(), west.left())
Output
True False
False 10 25
- Each call to
TicketBoothbuilds a new object and runs__init__once, so east starts with 40 seats and west with 25, each with 0 sold. east.sell(30): 0 + 30 is not more than 40, so east sells and hands back True.west.sell(30): 30 is more than 25, so it hands back False and sells nothing.east.sell(15): 30 + 15 = 45 is more than 40, so False. East has 40 − 30 = 10 seats left, and west still has all 25.- No call writes
self; Python passes each booth asselfautomatically.
Why it matters: each instance keeps its own state, and a method reaches that state through self.
Self-check questions
Each answer below was produced by running the line in Python 3.
- What does
print(7 // 2 * 2 + 7 % 2)show? Self-check: 7. Why: 7 // 2 = 3, then 3 × 2 = 6, and 7 % 2 = 1, so 6 + 1 = 7. - What does
print("ab"[::-1] * 2)show? Self-check:baba. Why: the step of −1 reverses the string toba, and* 2repeats it. - Which list does
list(range(10, 0, -3))make? Self-check:[10, 7, 4, 1]. Why: it counts down by 3 from 10 and never reaches the stop, 0. - With
nums = [5, 1, 4], what doesprint(nums.append(2))show? Self-check: None. Why: append changes the list in place and hands back None. - What happens when a program runs
t = (4, 9)and thent[0] = 1? Self-check: it stops with TypeError. Why: a tuple is immutable. - What does
print({"a": 1}.get("b"))show? Self-check: None. Why:.get()with no default hands back None for a missing key instead of raising KeyError. - What does
print("x,y,,z".split(","))show, and how many items is that? Self-check:['x', 'y', '', 'z'], 4 items. Why: cutting at each comma leaves an empty string between the two commas in a row. - Which exception type does
int("")raise? Self-check: ValueError. Why: the empty string is the right type, a string, but holds no whole number. - What does
print(type({}))show? Self-check:<class 'dict'>. Why: empty curly braces make an empty dictionary; an empty set needsset(). - Your friend runs binary search on
[40, 8, 27, 3]. What should you tell them first? Self-check: sort the list first. Why: binary search's precondition is a sorted list, and on an unsorted one it can miss an item that is there. - Which growth class is reading
items[-1]on a list of any length? Self-check: constant time, O(1). Why: one index read takes the same step count however long the list is. - A recursive function counts down from 3 to its base case at 0. How many frames are on the call stack at the deepest moment? Self-check: 4. Why: one frame each for 3, 2, 1, and 0, the same four frames
countdown(3)opens.
Check yourself — choose one. (a) Which unit in this guide felt least familiar, and which worked example would you redo first? (b) Pick one early habit, such as predict then run or read the error from the bottom up, and explain in two sentences how it changes the way you read one Weeks 9–15 program. Either choice is a good use of ten minutes.
Study plan
A short countdown sized to a course that meets twice a week. Count backward from the day the final opens in Week 16.
- Two weeks out: review Units 1–5 in one sitting and redo Worked examples 1 through 4 from scratch on paper, then run them. These ideas return inside later questions.
- Ten days out: review Units 6 and 7. Redo Worked examples 5 and 6 until you can say why each line prints what it does. Weeks 9 and 10 carry 11 of the 40 questions.
- One week out: review Units 8 and 9. Redo Worked examples 7 and 8 without looking. Week 12 carries 8 questions, the most of any week.
- Five days out: take the paired practice exam once, closed-book and under exam-like conditions, then sort every miss by week. Remember that it reaches into Weeks 1–7 far more than the final does.
- Four days out: review Units 10–12 and redo Worked examples 9 through 12, writing each trace line by line.
- Three days out: run the paired exam-prep tutorial (the copy/paste chatbot tutor, or its no-AI route) on the two units that felt weakest, with at least one from Units 6–12.
- Two days out: work the twelve self-check questions cold, then retake the practice exam.
- The day before: skim the "Predictable mistakes → cures" lines in every unit and watch one video from the list below. Sleep.
Use the exam-prep tutorial and the practice exam for active recall; this guide is for review, and those two are where you rehearse. Rereading alone does less for you than retrieval practice does.
Watch before the exam
These videos come from the course's own weekly readings. Use them to retrieve what you already learned and to connect weeks, not for new material.
- Nested Loops, Visually Explained — Visually Explained reviews Objective 4 from Week 6: the inner loop running in full on every outer pass.
- print vs return, Python's most common beginner mix-up — ludwig explains reviews Objective 5 from the Week 8 review; use it to connect Week 7's return to the methods of Week 15.
- Python Lists, Visually Explained — Visually Explained reviews Objective 6 from Week 9: indexing, slicing, and the list methods.
- Python Quick Tip, The Difference Between == and is, Equality vs Identity — Corey Schafer reviews Objective 6 from Week 9: aliasing, copying, and identity.
- Python Dictionaries, Visually Explained — Visually Explained reviews Objective 6 from Week 10; use it to link Week 4's conditions to a safe lookup.
- Python - The split() and join() methods - String Tutorial with Example — Appficial reviews Objective 7 from Week 11; use it to tie Week 3's indexing to the pieces split hands back.
- Python File Handling, Visually Explained — Visually Explained reviews Objective 7 from Week 12: writing, reading, and the with statement.
- Python Error Handling, Visually Explained — Visually Explained reviews Objective 7 from Week 12; use it to connect Week 1's error reading to try and except.
- Binary search in 4 minutes — Michael Sambol reviews Objective 8 from Week 13: halving a sorted list.
- Selection sort in 3 minutes — Michael Sambol reviews Objective 8 from Week 13; count the comparisons and connect them to Week 6's nested loops.
- Python For Beginners - Recursion Explained — TeachingCS reviews Objective 8 from Week 14: factorial's calls piling up on the stack and coming off as they return; its last run stops with RecursionError at Python's depth limit, which the video calls running out of memory.
- Why self is used in Python classes? — 2MinutesPy reviews Objective 8 from Week 15; use it to link Week 7's parameters to self.
Exam format and how it is graded
The final is graded automatically, and all 40 questions count the same. The questions appear one at a time, and you cannot go back to an earlier question once you move on. You have one attempt and no time limit. AI tools are not permitted on the final.
Test-taking strategies for this material:
- For any program, predict the output on scratch paper line by line before you look at the choices.
- Say the type before the value: does this line make an int, a float, a string, a list, or None?
- For a loop, write the value of each name at the top of every pass, and find where the test turns False.
- For a list or a dictionary, ask whether a line changes the object in place or builds a new one, and what the call hands back.
- For an error, decide whether the parser refused the text, the program raised partway through, or it ran and printed the wrong thing, then name the type the last line would show.
- For a search or a sort, write lo, hi, and mid, or the list after each pass, and count the comparisons as you go.
- For recursion, write each call on its own line, find the base case, then unwind from the deepest call back up.
- For a class, track each instance's attributes in its own column.
- Since you cannot go back, settle each question before you move on.
Built with The Course Maker · samples.thecoursemaker.com