Study notes used alongside this roadmap
Everything else on this site links out to the source. This page holds the reading notes used in between — material adapted from Harvard’s CS50P and Python’s own documentation, rearranged so each idea is explained before the syntax and then traced into how web applications are built. Adapted, not authored: the sources are credited below.
The Python chapters are adapted from Harvard University’s CS50P — its lecture notes and transcripts — together with Python’s official documentation. Harvard publishes CS50 course material under Creative Commons BY-NC-SA 4.0, and those chapters are shared under the same terms: attribution, non-commercial, share-alike. Not affiliated with, authorised by, or endorsed by Harvard University or CS50, and no replacement for the course itself. Nothing here is a solution to a graded problem set — the authoritative version is at cs50.harvard.edu/python.
The full stack sections follow the module structure of Full Stack Development with AI (AIM / Emeritus), using only its freely available curriculum outline. That course is not openly licensed and its own materials are not reproduced here; the module numbers are pointers back to it, and the resources listed under each are links to independent public documentation, each under its own licence.
Understanding CS50P and Its Full Stack Connections
A Python reading companion with web development perspectives for Tommy
Weeks 0–9 · Windows · No prior computer science required
This is an original teaching companion organized around Harvard's CS50P sequence. It is not an official Harvard publication or a transcript replacement. It draws on the official lecture notes, selected transcript passages, and Python's documentation. The examples and practice questions are original; they are not solutions to Harvard's graded problem sets.
You do not need to memorize this document. Use each chapter to understand what problem a feature solves, then watch that week's lecture and try small changes yourself. Later chapters are destinations, not prerequisites for Week 0.
Prepared September 2026
Contents
Select a chapter below, or use Word’s Navigation Pane. Read one week before its lecture; keep the other chapters as references.
Start here: how to read and learn
Week 0: functions and variables
Week 1: conditionals
Week 2: loops and collections
Week 3: exceptions
Week 4: libraries and interfaces
Week 5: unit tests
Week 6: file input and output
Week 7: regular expressions
Week 8: object-oriented programming
Week 9: additional tools and Python fluency
After the lectures: combine the ideas
Quick reference: confusing pairs
Start here: how to read and learn
The purpose of the course
Programming is the work of expressing a solution precisely enough for a computer to carry it out. Software engineering adds the responsibility of making that solution understandable, testable, maintainable, and useful to someone. CS50P develops an initial foundation in both programming and several engineering habits; it does not cover all of computer science.
Imagine a space-dodging game. It needs to remember a spaceship's position, decide what a key press means, update obstacles repeatedly, recognize collisions, calculate scores, and save progress. Those visible features are built from ordinary programming ideas. In this guide, the game is a recurring connection, not a prerequisite or a promise that these short examples draw a complete game.
| Lecture | Core capability | Example use |
|---|---|---|
| 0 | Name values and define actions | Store health and calculate a bonus |
| 1 | Make decisions | Choose a warning or game-over message |
| 2 | Repeat and organize work | Update a collection of enemies |
| 3 | Handle failed operations | Recover from invalid numeric input |
| 4 | Reuse libraries | Choose random positions and access services |
| 5 | Test behavior | Check scoring and movement rules |
| 6 | Read and write files | Save scores and process images |
| 7 | Describe text patterns | Validate a player-name format |
| 8 | Organize objects | Give each spaceship its own state |
| 9 | Use additional Python tools | Clarify interfaces and process data lazily |
Five ways to understand each concept
An analogy gives you a first impression. A precise definition limits that analogy. Code shows the notation. An execution trace shows what happens over time. A changed example checks whether you can transfer the idea. Use all five; an analogy alone can hide important details.
For each example, ask: What information exists before this line? What operation happens? What value is produced? What changes? Which line runs next? If you can answer those questions, you are understanding the program even when you still need a syntax reference.
Before watching, read the chapter's explanation and predict one example. During the video, pause when a concept changes, not at an arbitrary time interval. Afterward, recreate a small example and change a requirement. Then attempt the course's own exercises independently.
The practice questions have answers in the same chapter. Cover the answers first. A correct guess is weaker evidence than a correct explanation. If an answer surprises you, trace the execution again before adding more syntax to your study list.
Windows: know which place accepts which language
An editor is where you write a file. A terminal accepts commands that launch programs. The Python interpreter executes Python code. A file path identifies a location; the working directory is the folder relative paths are currently measured from.
Save Python instructions in a file such as lesson.py. Enter python lesson.py in a terminal opened in that folder. That terminal command is not a line to paste into the Python file. If you launch Python without a file, its interactive prompt normally shows >>>; there you can enter Python expressions directly. Do not copy the prompt symbols themselves.
Some Harvard demonstrations use a browser-based development environment. Its terminal and files belong to that environment, which can differ from your local Windows computer. Follow the meaning of a command rather than assuming every shell command has identical behavior everywhere. A shell is the program that interprets terminal commands; PowerShell is one example.
Your first vocabulary
Data is information a program works with. A value is a particular piece of data. A type describes what kind of value it is. A variable is a name referring to a value. Syntax is the grammar of code. An expression produces a value; a statement performs an instruction. Execution means running instructions. State means the current information held by a program. A bug is a mistake, and debugging is the investigation used to locate and correct it.
An algorithm is a step-by-step method. Pseudocode describes such steps informally before you commit to exact syntax. An implementation is the actual code realizing a method. Refactoring reorganizes code while preserving intended behavior. Optimization improves a measured resource cost, such as time or memory; shorter code is not automatically an optimization.
Sources: CS50P course overview, course sequence, Python on Windows, and Python glossary.
How the full stack readings fit CS50P
Follow CS50P Weeks 0 through 9 and then its final project. Each chapter retains the original Python teaching material and ends with a full stack perspective drawn from the README. The added material explains where the same idea appears in web applications, gives focused reading choices, and offers a small practice prompt.
CS50P week numbers identify course topics. README module numbers identify a separate resource catalog. A module can connect to several weeks, so a connection is not a claim that you have completed that full module. The README’s independent 26-week schedule is replaced here by the CS50P order.
Finish the core Python material and exercises first. Use a comparison when it clarifies the current idea. Resources marked as later work remain available after the course. No additional framework or deployed web project is required to move to the next CS50P week.
The sequence below is a learning recommendation. The official CS50P sequence is linked beside each weekly extension; the README supplies the additional resource catalog. Technology pricing, free-tier limits, and certificate comparisons are not part of this study mapping.
Week 0 — Functions and variables: M1 developer workflow, M2 web foundations, M3 JavaScript fundamentals, M11 Python.
Week 1 — Conditionals: M3 JavaScript decisions, M10 permission rules, M6 feedback.
Week 2 — Loops and collections: M3 JavaScript collections, M5 state, M9 data organization.
Week 3 — Exceptions: M8 API failures, M10 input and security boundaries, M11 validation, M17 unhappy paths.
Week 4 — Libraries and interfaces: M8 Node and Express, M11 Python and FastAPI, previews of M13 and M14 AI APIs.
Week 5 — Unit tests: M17 testing and code quality, M16 automated checks, testing across all projects.
Week 6 — File input and output: M9 databases, M15 document storage and retrieval, M18 durable project data.
Week 7 — Regular expressions: M6 form UX, M8 and M11 validation, M10 security distinctions.
Week 8 — Object oriented programming: M4 React components, M5 state ownership, M11 models, M9 entities.
Week 9 — Additional tools and Python fluency: M7 TypeScript, M11 Pydantic, M12 Next.js, M13 and M14 AI, M16 operations.
After the CS50P final project: complete the web prerequisites, then the README’s six web projects in dependency order. A final-project extension at the end records that sequence and its completion checks.
Week 0: functions and variables
Why this week exists
Before a program can do anything interesting, it must represent information and perform operations on it. This week introduces the vocabulary for that: values, variables, types, expressions, function calls, and returned results. You also begin separating your instructions into named pieces so that a larger task becomes easier to reason about.
Think of a function as a small service. You supply information, it performs some defined work, and it may hand back a result. This analogy has a limit: a Python function can also change other things, and a call always returns some value if it completes normally, even when that value is None.
Variables: a label, not a permanent equation
Consider this complete example:
health = 100
damage = 25
health = health - damage
print(health)
The output is 75. Python evaluates the right side of an assignment using the values that exist at that moment. It then binds the left-hand name to the resulting value. The third line is therefore: look up 100 and 25, subtract, then make health refer to 75.
| After this instruction | health | damage |
|---|---|---|
| health = 100 | 100 | Not yet defined |
| damage = 25 | 100 | 25 |
| health = health - damage | 75 | 25 |
Changing damage afterward does not recalculate health. Assignment records a result; it does not establish a spreadsheet formula that continuously updates. This distinction explains many beginner surprises.
For simple numbers, a labeled-box analogy is useful. More precisely, Python names refer to objects. An object is a value with a type and associated behavior. Later, two names may refer to the same mutable object, which is why the label analogy is more accurate than imagining that every variable owns a separate box.
Types determine which operations make sense
An int is an integer such as 7 or -2. A float represents a floating-point number such as 2.5; many decimal fractions are represented approximately. A str, pronounced string, is text. A bool is True or False, which becomes central next week.
print(12 + 3)
print("12" + "3")
print(int("12") + 3)
The outputs are 15, 123, and 15. Quotes create text. For strings, + concatenates, meaning joins text. int("12") converts suitable text to an integer; it does not change the original string in place. Conversion can fail if the text does not describe a supported integer.
The built-in input() function returns a string. Built-in means available without importing a module. If you ask for a speed and someone types 5, the initial result is the text "5". To calculate with it as an integer, you need conversion.
| Operator | Numeric example | Result |
|---|---|---|
| + | 7 + 2 | 9 |
| - | 7 - 2 | 5 |
| * | 7 * 2 | 14 |
| / | 7 / 2 | 3.5 |
| // | 7 // 2 | 3 |
| % | 7 % 2 | 1 |
| ** | 7 ** 2 | 49 |
// is floor division: it rounds the quotient downward, including for negative values. % is modulo, associated with the remainder. Parentheses can group calculations: (2 + 3) * 4 differs from 2 + 3 * 4. Floating-point rounding and displayed formatting are different operations; showing two decimal places does not change the stored value.
Reading a function from two perspectives
def add_bonus(score, bonus=10):
return score + bonus
points = add_bonus(40, 15)
print(points)
print(add_bonus(40))
From the caller's perspective, add_bonus(40, 15) is an expression that produces 55. The caller does not need to copy the addition logic each time. From inside the function, score and bonus are local names that refer to the supplied values.
def introduces a function definition. add_bonus is a name we chose. score and bonus are parameters: named input positions in the definition. The actual supplied values, 40 and 15, are arguments. The =10 in this definition provides a default when the second argument is omitted, so the last line displays 50.
The colon begins a block. Indentation identifies the block's body. Defining the function establishes its behavior but does not immediately run the body. Calling it with parentheses runs it. return ends that call and hands its value back.
Scope means where a name can be accessed. A parameter normally belongs to its function call. Passing a value explicitly helps make information flow visible. The name main is a common convention for a program's coordinating function; naming a function main does not automatically call it.
Displaying and returning are different
def announce_score(score):
print(score)
result = announce_score(50)
print(result)
This displays 50 and then None. The first display happens inside the function. Because the function reaches its end without explicitly returning a result, the caller receives None.
A side effect is an observable action beyond handing back a value, such as displaying text. A function can have side effects and return a useful result, but keep the two ideas separate. When a calculation returns a number, other code can display it, save it, or test it. Printing alone does not give the caller that number.
Strings, methods, and readable output
name = " Tommy "
clean_name = name.strip()
score = 50
print(f"Pilot {clean_name}: {score} points")
A method is behavior associated with an object. The dot in name.strip() accesses the string's method. strip() returns a new string without leading or trailing whitespace: spaces, tabs, and line breaks. It does not change name, because strings are immutable: their contents cannot be modified in place.
An f-string starts with f and evaluates expressions inside braces. Here the output is Pilot Tommy: 50 points. Braces in an f-string have a different purpose from braces used later to create dictionaries. Always read punctuation in context.
A comment begins with # outside a string and explains code to humans. Prefer explaining a reason or assumption over restating an obvious instruction. Pseudocode can also live in comments while you work out a solution.
Common wrong models
= does not ask whether values are equal; == does that. Quotes do not merely decorate a number; they produce text. A function definition is not a function call. print() does not return the thing it displays. A type conversion does not automatically rewrite the value held by an unrelated name.
When a code example gets shorter, ask whether it became easier to understand. Combining input, conversion, and calculation in one line may be correct, but you can keep intermediate names while learning.
Predict, change, explain
Question A: After x = 4, y = x, and x = 9, what is y? Question B: What does add_bonus(20, bonus=3) return? Question C: Change the string example so that it prints the original padded name, then explain why the spaces remain.
Answer A: y is 4. Rebinding x does not rebind y. Answer B: 23. bonus=3 supplies a keyword argument: an argument identified by its parameter name. Answer C: use name instead of clean_name in the f-string; the original string was never modified.
Before the lecture, you only need to recognize the information flow. After the lecture, aim to explain a function call, distinguish text from numbers, and write a tiny calculation with a returned result. A visual extension is to store a drawing distance in a variable and feed it to a drawing command.
Sources and further reading: Harvard Week 0 notes, lecture transcript, numbers and text, function definitions, built-in functions, and floating-point limitations.
Week 0 connections to full stack development
README connections: M1 developer workflow, M2 web foundations, M3 JavaScript fundamentals, M11 Python.
One calculation and several interfaces
A Python function can accept a price and quantity and return a total. Later, JavaScript can perform a similar calculation in a browser, and a backend can perform it after receiving a request. The reusable idea is an input and output contract: what a caller supplies and what the function promises to return. Keep calculation separate from asking for input or displaying a message.
Python and JavaScript are different languages
Variables, expressions, parameters, arguments, and return values provide a useful comparison with README Module 3. Similar-looking code can still behave differently across languages. First explain the Python example; then read the corresponding JavaScript example as a second perspective. Defer the DOM and events until you study the browser. The DOM is the browser representation of a page that JavaScript can inspect and change.
Where HTML and CSS fit
Module 2 has no direct CS50P equivalent. HTML describes page structure and CSS controls presentation. A terminal prompt and an HTML form are both ways to collect input, but they use different tools. Preview semantic HTML, meaning elements chosen for their purpose, now if that distinction helps. Flexbox, Grid, Tailwind, and responsive layout remain optional web study after CS50P.
A useful way to ask for help
Module 1 fits your workflow from the start. Ask for an explanation of one symbol, a predicted output, or a counterexample. Then explain the result in your own words and make a small change yourself. Keep the course exercise as the main task; the comparison readings are references to revisit.
Focused reading
From javascript.info or MDN JavaScript, select variables, data types, basic operators, and functions only. From MDN HTML, inspect a heading, label, input, and button. The full module catalog below is a reference shelf, not this week’s reading quota.
Python tutorial on introductory expressions
Try and explain
Describe a function that computes a shopping-cart subtotal. Identify its inputs, returned value, and which code should display it. Explain how that same calculation could serve a terminal program and a future web page.
Save for later
Return to the rest of M3 in Weeks 1 and 2. Build the README shopping cart or quiz only after learning JavaScript, browser events, and the DOM.
README resources for this connection
Module 1 Developer Mindset AI Powered Workflow
Topics to study in this area: how developers think and problem-solve, prompt engineering frameworks, AI toolkit (Cursor / Codex / ChatGPT), how the browser turns code into pixels.
Anthropic Prompt Engineering Guide
OpenAI Prompt Engineering Guide
Module 2 Web Foundations HTML CSS Responsive Design
Topics to study in this area: semantic HTML5, CSS3, Flexbox, Grid, Tailwind CSS, mobile-first responsive design.
MDN Learn: Structuring content with HTML
CSS-Tricks: Complete Guide to Flexbox
web.dev: Learn Responsive Design
Module 3 JavaScript Essentials Modern ES6
Topics to study in this area: syntax, functions, DOM manipulation, events, interactive UI.
Week 1: conditionals
Why a program needs decisions
A program with no choices would do the same sequence regardless of whether the player won, lost, or pressed a different key. A conditional chooses behavior based on a condition. A Boolean expression evaluates to True or False. Control flow is the route execution takes through instructions.
Imagine a decision tree at an entrance: if a ticket is invalid, show an error; otherwise, if the event has started, show the late-entry route; otherwise, show the main entrance. The order of those questions determines which instruction a person receives. Code behaves the same way: the ordering is part of the meaning.
Trace a chain, not just an individual condition
health = 20
if health <= 0:
status = "Game over"
elif health < 30:
status = "Low health"
else:
status = "Ready"
print(status)
Python asks whether 20 is at most zero. It is not, so it moves to elif. It asks whether 20 is below 30. That is true, so it assigns "Low health" and skips the remaining else branch. Finally it displays the status.
elif means otherwise-if. else is the fallback when earlier conditions were false. One chain runs at most one branch. By contrast, several separate if statements each get evaluated when execution reaches them.
| health | health <= 0 | Next relevant check | Final status |
|---|---|---|---|
| -5 | True | None | Game over |
| 0 | True | None | Game over |
| 20 | False | health < 30 is True | Low health |
| 30 | False | health < 30 is False | Ready |
If you put health < 30 first, zero would receive the low-health message before you reached the game-over check. Two conditions can overlap. Correct ordering handles that overlap deliberately.
Comparisons and combined conditions
== asks whether values are equal; != asks whether they differ. < and > mean less than and greater than. Adding = to either includes equality. These are comparison operators, meaning symbols that compare values.
health = 80
ammo = 0
can_fire = health > 0 and ammo > 0
print(can_fire)
The output is False: both comparisons must be true for this and expression to be true. With Boolean conditions, or requires at least one true condition, and not reverses truth. Parentheses can clarify a compound rule even when they are not required.
| A | B | A and B | A or B |
|---|---|---|---|
| False | False | False | False |
| False | True | False | True |
| True | False | False | True |
| True | True | True | True |
Python short-circuits these operations: it can skip the second operand when the first already determines the outcome. An operand is a value or expression an operator works on. For example, denominator != 0 and 10 / denominator > 2 avoids division when the denominator is zero.
With general objects, and and or return an operand rather than always constructing a Boolean. For this week's comparisons, focus on Boolean inputs. Later, learn truthiness: how objects such as empty strings and nonempty strings behave in conditions. Do not mistake this beginner truth table for the entire definition of and and or.
Turning a rule into a reusable function
def can_launch(health, fuel):
return health > 0 and fuel >= 10
print(can_launch(100, 9))
print(can_launch(100, 10))
The outputs are False and True. A comparison already produces a Boolean, so the function can return that expression directly. You do not need to write an if solely to return True and an else solely to return False.
Notice the separation of policy and presentation. The function represents a rule. Another part of the program can use its result to draw a button, print a message, or block a launch. This separation gives the rule more uses and makes it easier to test later.
Modulo, parity, and choosing alternatives
Modulo, written %, gives a remainder in the usual positive-integer examples. 10 % 3 is 1 because 10 is three groups of 3 with 1 left over. Parity means whether an integer is even or odd. number % 2 == 0 identifies even integers. In a game, modulo can also cycle through a sequence of colors or trigger behavior every few steps.
The lecture introduces match and case, which select behavior by matching a value to a pattern. In simple cases they provide a readable menu of alternatives:
command = "left"
match command:
case "left":
print("Move left")
case "right":
print("Move right")
case _:
print("Unknown command")
Here _ is a wildcard pattern that matches anything. This meaning differs from using _ as a throwaway loop-variable name. Pattern matching has more capabilities than this example, so avoid assuming it is merely another spelling of equality in every situation.
Common mistakes and practice
The expression command == "left" or "right" does not compare command with both strings. The second operand is the nonempty string "right", which is truthy. Use command == "left" or command == "right", or later use membership testing with in.
Question A: If the first health example used two separate if statements and a final else attached only to the second one, could zero receive an unintended status? Question B: Is 10 % 2 == 0 true? Question C: Write a rule that allows firing only when health and ammunition are both positive.
Answer A: yes. The second if is independent, so it can overwrite what the first did. An else belongs to its own surrounding conditional, not to every earlier if. Answer B: true, because the remainder is zero. Answer C: health > 0 and ammo > 0. Test it with zero ammunition, zero health, and positive values for both.
Before watching, ask which cases overlap and where equality belongs. After watching, practice boundaries: health exactly 0 or 30, fuel exactly 10. Boundary examples often reveal a wrong < or <= more clearly than ordinary inputs do.
Sources: Harvard Week 1 notes, lecture transcript, control flow, Boolean operations, and comparisons.
Week 1 connections to full stack development
README connections: M3 JavaScript decisions, M10 permission rules, M6 feedback.
A condition is a decision rule
The same branching idea appears in a quiz answer, a shipping discount, and a form error. Write the requirement first: for example, free shipping begins at a stated subtotal. Trace a value below the threshold, exactly at it, and above it. This boundary analysis exposes mistakes that one ordinary example can miss.
Transfer the idea without copying the syntax
Compare Python if, elif, and else with JavaScript conditional statements in M3. Boolean expressions express rules in both languages. Learn their comparison and truth-value behavior separately; the fact that both languages support conditions does not make all operators interchangeable.
Permission is also a rule
M10 later distinguishes identifying a user from deciding what that user may do. Authentication establishes identity; authorization checks permission. At this stage, reason about a plain-language rule such as whether a user owns a draft. This is conceptual preparation, not a complete security implementation.
The user needs to understand the branch
M6 adds the interface perspective. When a branch rejects a value, explain which requirement was missed and how to correct it. Choosing the correct branch and explaining its result are related responsibilities.
Focused reading
Revisit the M3 links in Week 0 for comparisons, logical operators, and conditional statements. Save the M10 implementation resources in Week 3 for later. Use the form-feedback discussion in Week 7 when the validation rules become more detailed.
JavaScript conditional statements
Try and explain
Write a decision table for a shipping discount. Include the exact boundary and an invalid negative subtotal. Explain whether invalid input should be handled before the discount rule.
Save for later
JWT, OAuth, passwords, payments, and production access control require separate backend and security study.
Week 2: loops and collections
Repetition is a program structure
If there are 200 asteroids, you do not want 200 separately written movement instructions. A loop expresses a repeated operation once. An iteration is one pass through it. A collection stores several items together; an iterable can supply items for a loop to visit.
There are two useful perspectives. A for loop asks, 'What item comes next?' A while loop asks, 'Should another repetition happen?' Neither is inherently more advanced. Choose the one that directly expresses the task.
A while loop is a repeated decision
countdown = 3
while countdown > 0:
print(countdown)
countdown -= 1
print("Launch")
| Before the check | Condition | Action | Value afterward |
|---|---|---|---|
| 3 | True | Print 3, subtract 1 | 2 |
| 2 | True | Print 2, subtract 1 | 1 |
| 1 | True | Print 1, subtract 1 | 0 |
| 0 | False | Leave the loop | 0 |
The condition is checked before every pass, including the first. Starting with zero would skip the body entirely. If you remove the subtraction, the value remains 3 and the loop continues indefinitely. This is an infinite loop. In a terminal program, Ctrl+C commonly interrupts execution.
The code prints immediately; it does not wait a second between numbers. Repetition and timing are separate concerns. Similarly, a game needs both repeated updates and a way to control when frames are drawn.
A for loop consumes items
enemies = ["asteroid", "drone", "comet"]
for enemy in enemies:
print(enemy)
The loop assigns one item at a time to the name enemy. You chose that name; it is not a Python keyword. enemies is a list, an ordered, mutable collection. Mutable means you can change its contents. An index is a position, beginning at zero for Python sequences: enemies[0] is "asteroid".
When you want a fixed number of repetitions, range supplies integers:
for step in range(2, 8, 2):
print(step)
The output is 2, 4, 6. The arguments mean start at 2, stop before 8, and advance by 2. The excluded stopping boundary is a common source of an off-by-one error: doing one too many or too few repetitions. range(3) produces 0, 1, 2; it does not build a full list of all those integers in advance.
for _ in range(3) is a convention for repeating three times when you do not need the counter. In this position, _ is still an ordinary variable name. Counting from zero is appropriate for indices; you do not have to force every real-world counter to begin at zero.
Lists, dictionaries, and the question you want to ask
scores = [30, 80, 50]
scores.append(90)
print(len(scores))
print(scores[1])
player = {"name": "Tommy", "health": 100}
player["health"] = 75
print(player["health"])
The outputs are 4, 80, and 75. append adds an item to the list's end. len returns its length. A dictionary associates keys with values. In player, the key "health" identifies 75 after the update. A key is a lookup label rather than necessarily a numeric position.
Use a list when order and repeated items matter. Use a dictionary when you need to retrieve information through a meaningful key. You can nest these structures: a list of dictionaries can represent many enemies, each with fields such as "x", "y", and "speed". A field is a named piece of information in a record.
Iteration over a dictionary normally visits its keys. for key, value in player.items() visits key-value pairs. The comma assigns the two parts to two names; this is unpacking, which Week 9 explores further.
Two names can share one list
first = [10, 20]
second = first
second.append(30)
print(first)
The output is [10, 20, 30]. Both names refer to the same list. second = first did not copy the collection. This is called aliasing. In contrast, rebinding second to a completely new list would not rebind first.
For a flat list of numbers, second = first.copy() creates a separate list container. This is a shallow copy: if a list contains other mutable objects, the contained references are still shared. You do not need deep-copy techniques yet, but knowing the limit prevents a misleading 'copy means everything is independent' assumption.
Nested loops and early exits
for row in range(2):
for column in range(3):
print(row, column)
The pairs are (0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2). For each outer iteration, the inner loop runs through its three columns. This is useful for grids, tiles, and image pixels. A pixel is an individual picture element in a digital image.
break exits the nearest enclosing loop. continue skips the remaining instructions in the current iteration and moves to the next iteration. A return inside a function exits the function itself, even if it is inside a loop. These instructions have different boundaries.
In this grid example, two rows times three columns gives six inner operations. Later, complexity analysis generalizes this reasoning to input sizes. Do not conclude that every pair of nested loops is quadratic: the actual number of repetitions matters.
Practice and explained answers
Question A: What does list(range(1, 5)) contain? Question B: If a list has three elements, is index 3 valid? Question C: What remains in first if, after the aliasing example, you execute second = []? Question D: How could you process only positive scores?
Answer A: [1, 2, 3, 4]. list collects the supplied items into a list. Answer B: no; valid nonnegative indices are 0, 1, and 2. Answer C: [10, 20, 30]; assigning a new list to second does not modify the previous list. Answer D: loop over the scores and run the desired operation inside if score > 0. This is filtering: retaining or processing items that satisfy a rule.
For a visual exercise, draw a small grid or repeating spiral. Before watching, predict the number of passes. Afterward, change the loop boundary and explain the exact effect. Avoid changing the size of a list while looping over it until you have studied the resulting behavior; collecting a new result is often easier to reason about.
Sources: Harvard Week 2 notes, lecture transcript, data structures, range and loop control, and aliasing FAQ.
Week 2 connections to full stack development
README connections: M3 JavaScript collections, M5 state, M9 data organization.
Collections model the thing you are building
A shopping cart contains multiple items. A task manager contains tasks with descriptions and completion status. A Python list models a sequence; a dictionary associates keys with values. Before choosing either, state what you need to do: keep order, find an item by a key, or visit every item.
Loops connect individual rules to a whole collection
A subtotal function handles one item; a loop can combine many items. Trace an empty cart, one item, and several items by hand. M3 later applies this idea to JavaScript arrays and objects. A Python dictionary and a JavaScript object are useful comparisons, but their exact behavior and permitted keys differ.
State means information that can change
Module 5 studies state management. You can begin with the underlying question now: who owns the current list of tasks? A completed-task count can be calculated from that list. Storing both values separately creates another consistency problem whenever a task changes. This is the idea of derived state: a value calculated from existing data.
The database connection comes later
M9 extends these questions to stored records and relationships. Today, sketch the fields of one task and explain how you would distinguish two tasks with the same title. Week 6 will revisit what must survive after the process ends.
Focused reading
Use the Week 0 M3 catalog for arrays, objects, and loops. The M5 catalog is in Week 8 and the M9 catalog is in Week 6. You only need their data questions now, not their frameworks.
React guidance on avoiding redundant state
Try and explain
Draw three task records on paper. Mark one complete, calculate the count, then remove a task. Explain which values are original data and which can be recalculated.
Save for later
Do not take React lists, query caching, or a database schema as requirements for this week. Work through Python collections first.
Week 3: exceptions
Failure is part of a program's input space
Your program can control its instructions, but it cannot guarantee that a user enters a number, that a file exists, or that a network is available. An exception is a signal that an operation encountered a condition it could not handle normally. Exception handling gives surrounding code a deliberate response.
Think of a delivery service. If a delivery address is missing, the driver reports the problem to someone who can resolve it. Silently claiming the package arrived would be wrong. Similarly, catching an exception should correspond to an action you can actually take: explain the issue, ask again, use a documented fallback, or stop clearly.
Three different kinds of mistake
| Kind | What happens | Example |
|---|---|---|
| Syntax error | Python cannot parse the instructions | A missing closing quote |
| Runtime exception | Execution reaches an operation that fails | int("comet") |
| Logic error | Execution completes with wrong behavior | Adding damage to health |
Parsing means interpreting the structure of text; Python must parse source code before executing it. A syntax error in the same script prevents normal execution of that script, so wrapping nearby lines in try does not repair malformed syntax. A logic error may raise no exception at all; tests and careful reasoning are needed to detect it.
A traceback lists the call path leading to a failure. Start with the final exception type and message, then inspect the indicated line and the values involved. The displayed line identifies where the problem surfaced, which is not always where its underlying cause began.
What try, except, and else actually do
raw_value = "comet"
try:
difficulty = int(raw_value)
except ValueError:
print("Please enter a whole number.")
else:
print("Difficulty:", difficulty)
int receives a string, but the string is unsuitable for this conversion, so it raises ValueError. Python abandons the remaining normal path through the try block and looks for an appropriate handler. The matching except runs. The else does not run because an exception occurred.
Change raw_value to "2": conversion succeeds, assignment completes, and the else displays Difficulty: 2. This is different from testing whether a Boolean condition is true. try asks the operation to run; except responds after a matching exception is raised.
If assignment fails, the new value was never assigned. Moving print(difficulty) outside the whole structure can therefore create another bug when no previous difficulty exists. Put success-only work in a success-only path.
Keep the try block focused. If many unrelated operations are inside it, the handler may catch a failure you did not intend to treat as invalid user input. Catch the types you understand rather than routinely catching every possible exception.
Combine conversion, validation, and retry
Conversion asks, 'Can this text become an integer?' Validation asks, 'Is that integer allowed here?' int("99") succeeds even if your game allows only difficulty 1, 2, or 3.
The following is a complete interactive example. It waits for keyboard input when run:
def ask_difficulty():
while True:
text = input("Difficulty (1-3): ")
try:
value = int(text)
except ValueError:
print("Use a whole number.")
continue
if 1 <= value <= 3:
return value
print("Choose 1, 2, or 3.")
chosen = ask_difficulty()
print("Selected:", chosen)
| User enters | Conversion | Range check | Result |
|---|---|---|---|
| comet | Raises ValueError | Not reached | Explain, ask again |
| 99 | Succeeds | False | Explain, ask again |
| 2 | Succeeds | True | Return 2 and leave function |
while True deliberately permits repetition until another instruction exits. continue starts a new iteration after conversion failure. return exits the entire function after valid input. Notice how previous weeks now work together: a function contains a loop, which contains exception handling and a condition.
The function name summarizes its promise: obtain an allowed difficulty. This is abstraction, meaning exposing a useful operation while allowing callers to ignore its internal details. The caller only needs the result and knowledge that the function interacts with the user.
Recognize important exception names
ValueError often means an operation received an acceptable kind of object with an unacceptable value. TypeError often means an inappropriate type or operation. NameError means a name could not be found. IndexError means a sequence position is unavailable. KeyError means a dictionary key is unavailable. FileNotFoundError means the requested file path could not be found.
pass is an instruction that does nothing. A handler containing only pass does not log, explain, repair, or retry anything by itself. A surrounding loop may retry, but the loop is responsible. Use a silent handler only when intentionally ignoring that particular condition is correct.
Later you will meet raise, which lets your own code signal an exception, and finally, whose block runs as control leaves a try statement under ordinary execution, including during exceptions and returns. A finally block does not make cleanup survive a forcibly terminated process or a power failure. Recognize these as later extensions rather than prerequisites for following this week's main examples.
Practice and answers
Question A: Does except ValueError catch every error? Question B: Why does 99 require a condition rather than the conversion handler? Question C: In the interactive example, what happens after entering 2.5? Question D: If code computes the wrong total without crashing, will adding try fix it?
Answer A: no; unmatched exceptions can propagate to surrounding callers. Propagation means the failure travels outward looking for a handler. Answer B: 99 is a valid integer but outside the application's allowed range. Answer C: int("2.5") raises ValueError; this conversion expects integer text, not decimal-point text. Answer D: no. You must correct the logic; exception handling is not a correctness checker.
Before watching, separate invalid syntax, failed operations, and wrong answers in your mind. Afterward, build one input function that explains what went wrong and returns only allowed values. This is already useful engineering: the program has a clear promise and a predictable response to mistakes.
Sources: Harvard Week 3 notes, lecture transcript, errors and exceptions tutorial, built-in exceptions, and try statement.
Week 3 connections to full stack development
README connections: M8 API failures, M10 input and security boundaries, M11 validation, M17 unhappy paths.
A failure needs a defined outcome
A text-to-number conversion can fail in Python. A web request can also carry missing or invalid values. The shared design question is what the caller should receive when the operation cannot proceed. Separate expected invalid input from an unexpected internal failure so that the user has an appropriate next step.
Conversion and validation answer different questions
Converting text to an integer establishes a representation. Validation checks whether that integer is allowed, such as a quantity greater than zero. Modules 8 and 11 later express these rules with tools such as Zod and Pydantic. An exception handler alone does not define the allowed values.
Security extends beyond handling exceptions
Module 10 connects through validation and permission rules. A successful conversion does not prove the caller is permitted to change a record. Password handling, OAuth, and payments also introduce requirements beyond this week. Treat these topics as a later specialist extension with their own documentation.
Recovery should match the cause
Asking again makes sense for mistyped terminal input. Repeating an operation blindly can be inappropriate for a persistent failure or an operation that already changed something. First identify what failed and whether retrying can duplicate an effect. Testing these failure paths connects directly to Week 5.
Focused reading
Read your Python exception examples first. For perspective, use the FastAPI handling-errors and request-body tutorials linked in Week 4. The M10 catalog records the later security track; it is not a request to implement login or payments this week.
Try and explain
For an item quantity, distinguish nonnumeric text, a negative number, zero, and a valid positive number. State what the user should see and whether the program should ask again.
Save for later
Study M10 fully after routing, persistence, and backend ownership checks. Revisit text-format rules in Week 7.
README resources for this connection
Module 10 Authentication Security Payments
Topics to study in this area: JWT, bcrypt, OAuth2 / Google login, OWASP Top 10, Stripe / PayMongo.
Week 4: libraries and interfaces
Reuse turns small programs into capable programs
A library is reusable code. A module is an importable unit, often a Python file. A package can organize related modules, while an installable distribution is the project delivered by a packaging tool. Everyday conversation often calls an installable distribution a package too. These terms overlap in casual use, but importing and installing are separate operations.
Imagine building a game controller from existing electronic components. You still design how the parts work together, but you do not manufacture every transistor. Libraries play a similar role: someone supplies a capability, and you work through its documented interface. An interface is the set of operations, inputs, and outputs through which software interacts.
Follow an imported function call
import random
obstacles = ["comet", "drone", "rock"]
chosen = random.choice(obstacles)
x_position = random.randint(0, 800)
print(chosen, x_position)
random is part of Python's standard library, which comes with Python. choice selects an item from a nonempty sequence; randint includes both endpoints. The exact output can vary. The dot accesses an attribute of the imported module; here that attribute is a function.
You may instead write from random import choice and call choice(obstacles). This binds the imported name directly in your module. It is primarily a difference in how names are accessed; it is not a promise that Python loads only that function's code. Avoid wildcard imports while learning because they make the origin of names less obvious.
random.shuffle(items) changes a list in place and returns None. By contrast, random.choice(items) returns a selected item. Always read a function's return behavior; similar-looking library calls can have different effects.
The statistics module provides functions such as mean, the arithmetic average. You do not have to memorize every available function. Learn to search for a task, then inspect arguments, return value, possible errors, and a small example.
Install in the environment you actually use
An environment is a Python interpreter together with the packages available to it. A virtual environment isolates a project's installed dependencies. A dependency is external code a project requires. pip installs distributions, often from PyPI, the Python Package Index.
For a Windows project, these PowerShell commands create an environment and install requests into it without requiring activation:
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install requests
The -m option asks Python to run an installed module as a program. .venv is a directory name chosen by convention. The second command explicitly selects the environment's Python, avoiding confusion about which interpreter receives the package. You would use that interpreter to run the project's scripts, or select it in your editor.
Installing makes a distribution available in that environment. Importing makes its module names available to running code. import requests alone cannot install a missing package. Conversely, installing it does not insert an import statement into your source file.
Inputs can arrive at launch time
When someone launches python game.py easy, the word easy is a command-line argument. sys.argv provides a list: element zero is normally the script name and later elements are the supplied arguments.
This file-based example must be launched with a suitable argument:
import sys
if len(sys.argv) != 2:
sys.exit("Usage: python game.py DIFFICULTY")
difficulty = sys.argv[1]
print("Requested:", difficulty)
sys.exit stops execution by raising SystemExit. A usage message explains how to invoke a program. Checking length before indexing prevents an unavailable-position error. A slice such as sys.argv[1:] selects the arguments after the script name; the omitted ending boundary means continue to the end.
APIs and structured responses
API stands for Application Programming Interface. It is a defined way to use software capabilities. A library has an API; a web service can expose one too. A request asks a service to perform an operation or return information, and a response carries the result.
HTTP, Hypertext Transfer Protocol, is a set of communication rules used by the web. A URL is an address identifying a resource. The requests library helps send HTTP requests. Harvard demonstrates an external song-search API, showing that your program can consume information supplied by another system.
JSON, JavaScript Object Notation, is a text format for structured information. JSON is not a Python dictionary, although decoding a JSON object commonly produces a dictionary in Python. Its arrays commonly become lists. Parsing interprets text according to a format; decoding translates a representation into usable values.
This offline example lets you inspect the idea without needing a live service:
import json
response_text = '{"name": "Comet", "speed": 4}'
settings = json.loads(response_text)
print(settings["name"])
print(settings["speed"] + 1)
The outputs are Comet and 5. The JSON string is parsed into a dictionary, then ordinary lookup and arithmetic apply. A real network program must also consider timeouts, failed HTTP responses, malformed data, and whether the expected fields exist. A timeout limits how long an operation waits according to the networking tool's rules. Those details are extensions beyond the simplest lecture demonstration.
Making your own module without unwanted startup behavior
The same .py file can be run directly or imported by another program. Top-level statements execute when a module is first imported normally. Therefore, a reusable module should not unexpectedly launch a game or ask for input during import.
def add_bonus(score, bonus):
return score + bonus
def main():
print(add_bonus(40, 10))
if __name__ == "__main__":
main()
name is a special module variable. When this file is run directly, its value is "main"; when imported normally, it is the module's name. This guard runs the demonstration only in the direct-run case. The double underscores are part of the exact spelling. This pattern becomes especially helpful when importing functions into tests.
Practice and answers
Question A: Why might pip install requests succeed but your editor still report an import failure? Question B: What does items = random.shuffle(items) do to the name items? Question C: Why should a test be able to import a scoring function without launching a game?
Answer A: installation and execution may be using different environments. Check the selected interpreter. Answer B: the original list is shuffled, then items is rebound to None, the return value. Answer C: tests need the rule independently from interactive startup behavior; separating those concerns makes code reusable and predictable.
Before watching, ask what belongs to Python itself, what belongs to a library, and what you must write. A visual extension is using a graphics library to draw objects whose position and color you control. The library handles rendering, meaning producing the picture; your program supplies behavior.
Sources: Harvard Week 4 notes, lecture transcript, Python modules, random, venv, package installation, JSON, and Requests quickstart.
Week 4 connections to full stack development
README connections: M8 Node and Express, M11 Python and FastAPI, previews of M13 and M14 AI APIs.
Libraries run within your program and services run elsewhere
Importing a Python library makes its code available to your program. Calling an HTTP API communicates with another service. Both expose an interface, meaning a defined way to request behavior, but a remote service can add network delay and failures. Read required inputs, returned data, and error behavior before relying on either.
A client and a server are roles
A client sends a request; a server handles it and returns a response. Express in M8 and FastAPI in M11 are two ways to build the server side. Python functions provide useful preparation for understanding a request handler. CS50P Week 4 introduces library use and API consumption; building a server is an extension.
JSON is a representation crossing a boundary
JSON is a text format for exchanging structured data. After parsing a response, your program works with language values such as dictionaries and lists. Receiving JSON does not guarantee that it has the fields or types you expected. Examine a small example and trace the text-to-data step.
An AI feature still has an interface
Modules 13 and 14 later add generated responses. The surrounding program must decide what input to send, how to handle failure, and what output is acceptable. First learn one ordinary API call and its result. Streaming, conversation history, and model-specific behavior are later layers.
Focused reading
Use the Python Tutorial for modules and the existing CS50P API examples first. Preview the FastAPI first-steps and request-body sections after you can explain an ordinary Python function. Express is a comparison for the later JavaScript backend track, not a second framework to learn simultaneously.
FastAPI request bodies and validation
Try and explain
Draw the path from a weather question to a request, a response, parsed data, and displayed text. Identify which steps happen locally and which depend on another service.
Save for later
Return to FastAPI models after Weeks 8 and 9. Complete JavaScript and HTTP prerequisites before implementing the Express project. AI resources are collected in Week 9.
README resources for this connection
Module 8 Node js Express
Topics to study in this area: Node runtime/event loop, Express structure and routing, middleware, CORS, error handling, status codes, Zod validation, Swagger/OpenAPI.
Module 11 Python for Backend Data APIs
Topics to study in this area: Python fundamentals, FastAPI, Pydantic, CORS, React + FastAPI integration.
Week 5: unit tests
A test turns an expectation into an executable check
A unit test checks a small unit of behavior, often a function. Think of it as a repeatable experiment with known inputs and an expected result. You still decide which behavior is correct. A testing tool cannot invent your requirements or prove that an unspecified requirement was met.
A manual check is useful while exploring. Automated tests are useful when you want the same checks repeated after each change. A regression is behavior that used to work and breaks after an update. Tests can reveal regressions before someone encounters them in the application.
Start with a clear contract
A contract states what inputs a function accepts and what result or failure it promises. Consider this rule: moving left subtracts a nonnegative distance from a nonnegative position, but the result must not cross the left boundary at zero.
def move_left(position, distance):
if position < 0 or distance < 0:
raise ValueError("Use nonnegative values")
return max(0, position - distance)
raise deliberately signals an exception. max chooses the larger result, enforcing the zero boundary. This example assumes numeric arguments; the contract here does not promise automatic conversion from arbitrary text.
| Test input | Expected behavior | Reason |
|---|---|---|
| position 20, distance 5 | Return 15 | Ordinary movement |
| position 20, distance 20 | Return 0 | Exactly reach boundary |
| position 20, distance 25 | Return 0 | Clamp at boundary |
| position 0, distance 5 | Return 0 | Already at boundary |
| position 20, distance -1 | Raise ValueError | Reject invalid distance |
An edge case is a boundary or unusual input likely to expose assumptions. These examples are selected for different reasons, not merely because more tests sound impressive.
Assertions and pytest
def test_move_left():
assert move_left(20, 5) == 15
assert move_left(20, 20) == 0
assert move_left(20, 25) == 0
assert move_left(0, 5) == 0
These tests assume move_left is defined or imported. assert raises AssertionError if its condition is false. If the assertions pass, they normally print nothing. Silence from an assertion is different from a testing tool's summary.
pytest is a third-party test runner: it discovers suitable test functions, runs them, and reports results. Put application code in game_rules.py, put test functions in test_game_rules.py, and import the rule in the test file with from game_rules import move_left.
After installing pytest in the project environment, run its Python with -m pytest, for example ..venv\Scripts\python.exe -m pytest. Names beginning with test_ are part of pytest's discovery conventions. A tests directory does not universally require an init.py file; details depend on how imports and the project are organized.
Test expected failures too
The following belongs in a test module after importing both pytest and the function under test:
import pytest
def test_negative_distance():
with pytest.raises(ValueError):
move_left(20, -1)
The test passes when the enclosed operation raises the expected exception. An exception is therefore sometimes correct behavior, not always a failure of the test. with manages a context; here pytest uses it to observe the expected failure. Week 6 uses the same statement with files for a different purpose.
Each test function is a separate test unit for the runner. Within one function, a failed assertion prevents its later assertions from executing. By default, other test functions can still run. Splitting independent behaviors into clearly named tests improves diagnosis.
Why returning values makes testing easier
A function that calculates movement and returns a position can be tested without a window, a keyboard, or a running game. If that same function reads keyboard input, draws the screen, plays a sound, and saves a file, checking movement alone becomes much harder.
This suggests a useful design: keep the core rules in functions that accept values and return values, and place interaction around them. A pure function depends only on its inputs and produces no side effects. Not every function should be pure, but pure calculations are often easy to reason about and test.
The pattern 'arrange, act, assert' means prepare the input, perform the operation, and compare the result with the expectation. For a tiny function, those steps can fit on one line. For larger behavior, making them visible helps readers understand the experiment.
What passing tests do and do not establish
Passing tests establish that the tested cases met their expectations under the test conditions. They do not establish that all possible inputs are handled or that the expectations themselves are correct. A test that copies the implementation's formula may reproduce the same mistake. Derive expected results independently from the rule.
Do not rely on Python assertions for essential user-input validation: Python can disable assertions under optimization settings. Use explicit conditions and suitable exceptions for required runtime checks. Tests and runtime validation serve different purposes.
Practice and answers
Question A: A broken implementation returns position - distance without max. Which test above exposes it? Question B: If test_move_left fails at its first assertion, are its remaining assertions executed? Question C: Why test zero as well as a typical positive value?
Answer A: move_left(20, 25) would return -5, and move_left(0, 5) would return -5. Both violate the boundary rule. Answer B: no; execution leaves that test at the raised assertion failure. Other discovered test functions can still run. Answer C: boundaries often follow a different branch or reveal an incorrect comparison.
Before watching, write one sentence specifying a function's behavior. After watching, choose ordinary, boundary, and invalid inputs for it. A visual project connection is testing score and collision calculations before attaching them to animation. Collision means objects overlap or contact according to the game's chosen geometric rule.
Sources: Harvard Week 5 notes, lecture transcript, pytest introduction, pytest discovery, and assert semantics.
Week 5 connections to full stack development
README connections: M17 testing and code quality, M16 automated checks, testing across all projects.
A test checks behavior against an independent expectation
Your CS50P unit tests make a rule executable. The same reasoning carries into Vitest for JavaScript and API tests in M17. Expected results should come from the requirement and hand reasoning, rather than from copying the current implementation’s output.
Different tests observe different boundaries
A unit test checks a small part in isolation. An integration test checks cooperating parts. An end-to-end test follows a larger user path. The README’s Vitest, Supertest, Testing Library, and Playwright resources address different parts of that picture; they are not interchangeable names for pytest.
A passing tool does not prove the entire product
A formatter changes presentation. A linter flags selected suspicious patterns. Tests examine chosen examples. Later, a type checker examines type relationships. These checks provide different evidence, and none establishes every requirement on its own.
Automate a check after you understand it
M16 adds continuous integration, or CI: running checks automatically when code changes. The first milestone is a local test you can explain, including what a failure means. A pipeline adds repeatability to that check; it does not improve an incorrect expected result.
Focused reading
Work through pytest and the companion’s testing chapter. Browse the introductory M17 resource descriptions to distinguish the test levels. Save JavaScript tool installation and the M16 catalog in Week 9 for the relevant project.
Try and explain
Choose a movement or subtotal function. List ordinary, boundary, empty, and invalid cases that apply. Compute the expected results before running the function, and explain why each case matters.
Save for later
System design, browser automation, monitoring, and CI configuration are extensions. Keep testing your Python work from this week onward.
README resources for this connection
Module 17 Testing Code Quality System Design
Week 6: file input and output
From temporary state to saved information
I/O stands for input/output. A file can supply input to a program or receive output from it. Variables hold working information during execution; a file can preserve information beyond that execution. Persistence is the ability to retain data for later use.
Think of working memory as your current workspace and a saved file as a recorded result you can reopen. This is an analogy: operating systems manage memory and storage in more complicated ways. For this chapter, the important distinction is that assigning a variable does not automatically save it for the next program run.
Writing text and managing a resource
high_score = 250
with open("high_score.txt", "w", encoding="utf-8") as file:
file.write(str(high_score))
open creates an object through which you access the file. "w" requests text writing and truncates an existing file: it replaces its previous contents. If the file does not exist, it is created when possible. str converts the number to text, because this text file's write method expects a string.
with uses a context manager, an object that manages entry to and exit from a block. Here it closes the file when the block exits, including through ordinary exception handling. The name after as is the name you use inside the block. Resource means something the program acquires and should release, such as an open file.
An encoding is a rule for translating text into bytes and back. UTF-8 is a widely used encoding. A byte contains eight bits, and a bit is a binary digit. All files are ultimately stored as bytes; a text file is one you interpret using a text encoding. Binary mode works with bytes directly rather than automatically decoding text.
Read, convert, then use
Run this after the previous writing example:
with open("high_score.txt", "r", encoding="utf-8") as file:
saved_text = file.read()
loaded_score = int(saved_text)
print(loaded_score + 10)
The output is 260. Reading returned text, not an integer. The sequence is acquire file, read representation, close file, convert representation, calculate. If the file contains "comet", the conversion fails. If the file is missing, opening it fails. These are different problems and may deserve different handling.
| Mode | Meaning | Existing content |
|---|---|---|
| r | Read text | Preserved |
| w | Write text | Replaced |
| a | Append text | Preserved, new text added |
| rb | Read bytes | Preserved |
The filename above is a relative path: it is resolved from the current working directory, not automatically from the script's folder. An absolute path identifies a location starting from the system's root or drive. When a program cannot find a file you can see, check which directory it is actually using.
Lines and newline characters
\n represents a newline character in an ordinary string. Text returned while iterating over a file often retains its newline. Since print also adds a newline by default, printing such text unchanged can create apparent blank lines. print(line, end="") avoids adding another newline, or remove only the newline when that matches your purpose.
Be deliberate with strip(): it removes surrounding whitespace, which may include meaningful spaces. Data cleanup should follow the data's rules rather than remove anything that looks inconvenient. Iterating over a file processes one line at a time; read() loads its full contents into a string. A very large file may therefore call for a different approach.
Structured files and CSV
CSV means comma-separated values. It represents rows containing fields, somewhat like a simple spreadsheet table stored as text. A record is one row's logical information. Fields can contain commas inside quotes, which is why manually using line.split(",") is not a general CSV parser.
import csv
players = [
{"name": "Tommy", "score": 250},
{"name": "Pilot, Two", "score": 180},
]
with open("scores.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=["name", "score"])
writer.writeheader()
writer.writerows(players)
DictWriter writes dictionaries as rows using the specified field order. writeheader() writes the field names. The CSV library handles the comma inside the second name. newline="" lets the CSV module manage newline handling as documented, which is particularly helpful on Windows.
To read that file:
with open("scores.csv", newline="", encoding="utf-8") as file:
reader = csv.DictReader(file)
restored = list(reader)
print(restored[1]["name"])
print(int(restored[0]["score"]) + 1)
The outputs are Pilot, Two and 251. Dictionary lookup by header name makes the meaning clearer than memorizing column positions. CSV readers normally return field contents as strings, so convert numeric fields deliberately.
Sorting rules, lambdas, and image files
Suppose players is the list above. sorted(players, key=lambda player: player["score"], reverse=True) returns a new list ordered by descending score. A key function supplies the comparison value for each item. A lambda is a small function written as one expression without a separate def name. You could supply a named function with the same behavior instead; neither is automatically better in every context.
The lecture also processes images using Pillow, whose modules are imported through PIL. An image is not merely ordinary text with a different extension; its format defines how bytes describe dimensions, colors, and other information. A frame is one picture in a sequence. An animated GIF stores frames displayed in sequence to create motion.
This is a direct route to something visual: combine existing images into an animation, inspect a picture's size, or transform a picture using documented library operations. The file extension is a naming convention; reading a file correctly also depends on its actual format.
Practice and answers
Question A: What happens if you save a second score with mode w? Question B: Why does a score read from CSV need conversion? Question C: Why might splitting a row at every comma break "Pilot, Two"? Question D: Does sorted(players, ...) rearrange players in place?
Answer A: it replaces the earlier file contents. Answer B: the normal CSV representation supplies text fields rather than typed Python integers. Answer C: the comma belongs inside a quoted field, not between two fields. Answer D: no, sorted returns a new list; a list's .sort() method sorts that list in place and returns None.
Before watching, identify the representation at each step: bytes, text, or Python values. Afterward, save a small score table and read it in a fresh run. This verifies persistence instead of merely showing that an in-memory variable still exists.
Sources: Harvard Week 6 notes, lecture transcript, input/output tutorial, CSV documentation, sorting HOWTO, and Pillow tutorial.
Week 6 connections to full stack development
README connections: M9 databases, M15 document storage and retrieval, M18 durable project data.
Persistence means data survives a later run
A list exists in the running program. A file can retain a representation after the program exits. Module 9 extends persistence to a database, which also provides mechanisms to query data and enforce relationships. A CSV file is useful preparation but is not equivalent to a relational database.
The data model comes before the storage tool
A data model describes the records, fields, and relationships your application needs. For a task tracker, decide what identifies a task and what completion means before picking CSV or PostgreSQL. Later, constraints can enforce rules such as unique identifiers and references to existing owners.
Loading and saving are transformations
Trace the path from Python values to saved text, then back into values on the next run. Type conversion, missing files, and malformed records are separate questions. A successful write is only part of the requirement; check that a new run can reconstruct the intended information.
Retrieval adds a selection problem
M15 introduces retrieval-augmented generation, or RAG: finding relevant material and supplying it to a model as context. Reading files is an early prerequisite. Chunking divides material into pieces; embeddings represent material numerically for similarity search. Those additional techniques require their own study after storage and API basics.
Focused reading
Use the companion’s file and CSV examples first. SQLBolt and the official PostgreSQL tutorial provide an optional introduction to rows, keys, and queries. The M15 links are a future reference; do not build a vector database during the file lesson.
Python reading and writing files
Try and explain
Sketch a task record, save a tiny example in a format you understand, and describe how a new run would restore it. Explain what should happen if a required field is absent. For future search, write two questions and identify which saved passages would answer each.
Save for later
PostgreSQL, Prisma, migrations, indexing, and vector search are not covered by CS50P File I/O. Revisit them in the backend and AI extension stages.
README resources for this connection
Module 9 Databases SQL NoSQL
Topics to study in this area: relational vs NoSQL, PostgreSQL, Prisma ORM, data modeling, migrations.
Modules 15A 15B Vector Databases RAG
Topics to study in this area: embeddings, Supabase pgvector, chunking, RAG architecture, knowledge-base chatbot.
Anthropic Cookbook — RAG recipes
Week 7: regular expressions
Describe a rule instead of listing every permitted string
A regular expression, or regex, describes a pattern in text. It can help validate a format, extract a portion, or replace matching portions. Validation asks whether data meets a rule. Extraction selects information from a larger input. Substitution replaces selected text.
Imagine a stencil: you describe allowed positions and repetitions, then ask whether text fits. Unlike a physical stencil, a regex can allow variable lengths and alternatives. The analogy helps you begin, but the exact pattern rules determine the result.
Regex is its own notation embedded in Python strings. A plus sign inside a regex pattern means something different from a plus sign used to add Python numbers. Your first job is to keep the two levels separate: Python constructs the string, and the regex engine interprets its pattern. An engine is the software implementing that matching behavior.
Begin with the requirement in ordinary language
Suppose your game allows usernames containing 3 to 12 English letters, digits, or underscores, with no spaces. This is a product choice for this exercise, not a universal rule for human names or all usernames.
import re
pattern = r"[A-Za-z0-9_]{3,12}"
for username in ["Tommy_14", "ab", "Pilot Two", "Comet"]:
accepted = re.fullmatch(pattern, username) is not None
print(username, accepted)
The outputs pair the names with True, False, False, and True. fullmatch requires the whole string to fit. On success, it returns a match object containing details; on failure, it returns None. is not None explicitly checks that the result is not that absence value.
[A-Za-z0-9_] is a character class: it matches one character from the specified ranges or underscore. {3,12} repeats that component between three and twelve times. The r marks a raw string, which preserves backslashes for pattern interpretation. There are no backslashes in this particular pattern, but the convention makes later patterns less confusing.
Build a small pattern vocabulary
| Regex component | Default meaning | Example |
|---|---|---|
| [0-9] | One digit from 0 to 9 | Matches the 7 in 7 |
| . | Any one character except newline | a.c matches abc |
| * | Zero or more repetitions | ab* matches a or abbb |
| + | One or more repetitions | ab+ requires at least one b |
| ? | Zero or one repetition | ab? matches a or ab |
| {m,n} | Between m and n repetitions | [0-9]{2,4} |
| (...) | Capture a matching group | ([0-9]+) |
| . | A literal dot | A period in a filename |
^ normally anchors a match at the start of a string. $ normally anchors at the end or just before a final newline. Flags can change these behaviors, which is one reason fullmatch communicates whole-input validation clearly. A flag is an option changing how matching operates, such as re.IGNORECASE for ignoring letter case.
\w matches Unicode word characters plus underscore in ordinary Python string patterns; it is broader than only English letters and digits. Unicode is a system for representing text characters across writing systems. Use explicit ranges when your rule truly restricts input to those ranges. Do not accidentally treat a simplified pattern as a complete rule for every language.
Search, match, and fullmatch answer different questions
text = "score=250"
print(re.search(r"[0-9]+", text) is not None)
print(re.match(r"[0-9]+", text) is not None)
print(re.fullmatch(r"[0-9]+", text) is not None)
The results are True, False, False. search finds a match anywhere. match starts at the beginning. fullmatch requires the entire string to match. This distinction explains why checking that a string contains a valid-looking fragment can accidentally accept a much larger invalid input.
Before adding more punctuation to a pattern, verify you chose the right operation. An exact-substring test such as "@" in text or a prefix test such as text.startswith("score=") may be enough for a simple task. Regex is useful when the pattern itself adds value, not because every text task must use it.
Capturing information instead of only accepting it
message = "score=250"
match = re.fullmatch(r"score=([0-9]+)", message)
if match is not None:
score_text = match.group(1)
score = int(score_text)
print(score + 10)
The parentheses define capturing group 1. group(0) would refer to the whole matched text. The output is 260. Even though the captured characters describe digits, the group returns a string; numeric conversion is still a separate operation.
For substitution, re.sub(r" +", " ", "Pilot Two") returns "Pilot Two". The pattern describes one or more ordinary spaces, and the replacement supplies one. This is normalization: bringing equivalent-looking inputs into a consistent representation. Choose normalization rules deliberately because some distinctions may matter.
Greediness and layered escaping
A greedy quantifier tries to match as much as it can while allowing the entire pattern to succeed. Quantifiers are repetition operators such as * and +. A following question mark can request non-greedy behavior, preferring as little as possible. For example, the pattern <.> can span from the first < to the last > in a line; <.?> prefers an earlier closing bracket. This example illustrates matching behavior, not a recommendation to parse arbitrary HTML with regex.
An escape gives punctuation a special meaning or removes its special meaning. Python's ordinary string "\n" contains one newline character; the raw string r"\n" contains a backslash and the letter n, which a regex engine can then interpret. Raw strings simplify this layering, but they have syntax limits, such as not ending with a single unpaired backslash.
The lecture also introduces :=, called the walrus operator, which assigns within an expression. You might see if match := re.search(pattern, text):. It combines obtaining a match with testing its truthiness. Keep separate assignment and checking while that is easier to read; compactness is optional.
Practice and answers
Question A: Why does re.search accept a digit pattern inside "score=250" while fullmatch rejects the same whole string? Question B: Does a regex checking an email's shape prove the mailbox exists? Question C: Modify the username length rule to allow 4 through 16 characters. Question D: Why does . need escaping when you mean a literal period?
Answer A: search can choose a substring; fullmatch must account for every character. Answer B: no; shape validation is different from existence or ownership verification. Answer C: replace {3,12} with {4,16}. Answer D: unescaped dot normally stands for any non-newline character, so it admits more than a period.
Before watching, translate each pattern into ordinary words. Afterward, create accepted and rejected examples, especially short, long, and almost-correct inputs. If you cannot explain why a pattern rejects something, simplify it until you can. Regex fluency comes from understanding small components and checking examples, not memorizing giant expressions.
Sources: Harvard Week 7 notes, lecture transcript, regular-expression reference, regex HOWTO, and assignment expressions.
Week 7 connections to full stack development
README connections: M6 form UX, M8 and M11 validation, M10 security distinctions.
A pattern describes text shape
Regex can check or extract a text pattern. A form also has rules beyond shape: whether a value is in range, whether a record exists, and whether the user is allowed to use it. Distinguish syntax checks from these other requirements before choosing a pattern.
Validation needs a useful interface
Module 6 connects this week’s rules to labels, instructions, and field-level errors. Tell a user the requirement before they submit and preserve their input when a correction is needed. React Hook Form belongs to a later React implementation; the design questions can be understood now.
One requirement may need several checks
A name could match your permitted-character rule and still be unavailable. An email-like string can match a pattern without proving control of that inbox. This is why a regex is only one tool within broader validation and verification.
Patterns need counterexamples
For every accepted example, look for a nearby rejected one: a missing character, extra suffix, unexpected whitespace, or an empty string. Decide deliberately whether you need a whole-string check or a match somewhere inside the text. Week 5’s testing habits apply directly.
Focused reading
Use the CS50P regex chapter first. From M6, preview form usability and accessible component guidance as a second perspective. Revisit M10 in Week 3 for the distinction between matching input and enforcing permissions.
Python regular expression reference
Try and explain
Write a short player-name rule in English. Give three accepted and three rejected examples. Draft the message a user would see for one rejected example before attempting the pattern.
Save for later
Figma, shadcn/ui, Radix, and full responsive interface design remain separate practice. Complete the HTML and React prerequisites before building with React form libraries.
README resources for this connection
Module 6 UI UX Principles AI Assisted Design
Topics to study in this area: design thinking, Figma wireframing, shadcn/ui, Radix UI, React Hook Form, v0.dev.
Week 8: object-oriented programming
A new organization for familiar ideas
Object-oriented programming, abbreviated OOP, groups related data and behavior. It does not replace variables, loops, conditions, or functions; it organizes them. A programming paradigm is a general approach to structuring programs. Procedural programming emphasizes sequences of operations and functions; OOP emphasizes objects with associated behavior. Python supports several approaches, which can coexist.
Suppose a game has many spaceships. Each needs a name and health, and each can take damage. Keeping unrelated lists of names and health values requires you to preserve their correspondence. A dictionary per ship improves this. A class can express the repeated structure and operations more explicitly when that organization helps.
Class, instance, attribute, method
class Spaceship:
def __init__(self, name):
self.name = name
self.health = 100
def take_damage(self, amount):
if amount < 0:
raise ValueError("Damage cannot be negative")
self.health = max(0, self.health - amount)
first = Spaceship("Comet")
second = Spaceship("Nova")
first.take_damage(20)
print(first.health, second.health)
A class defines a type of object. An instance is a particular object of that class. An attribute is information or behavior accessed through an object. A method is a function associated with a class or object. The output is 80 100: the first ship changed; the second did not.
The blueprint analogy is useful: the class describes a design, and instances are individual ships. Its limit is that a Python class is itself an object with capabilities; it is more than inert paper. For now, focus on the distinction between defining a kind and creating a particular instance.
Follow object creation and a method call
When Spaceship("Comet") executes, Python creates an instance and calls its initializer, init, with the new instance and the supplied name. More precisely, init initializes an already-created instance; object allocation is a separate part of construction. You do not need to customize allocation for ordinary beginner classes.
self is the conventional name of the first parameter of an instance method. In first.take_damage(20), Python supplies first automatically as that parameter, and 20 becomes amount. self is not the class as a whole and not a special universal variable; it is the instance received by that method call.
| Expression or instruction | Meaning |
|---|---|
| Spaceship | The class object |
| Spaceship("Comet") | Create and initialize an instance |
| self.name = name | Store this call's name on this instance |
| first.health | Read this particular ship's health |
| first.take_damage(20) | Run a method on this particular ship |
Names beginning and ending with double underscores are often called dunder names. Special methods such as init participate in Python-defined operations. The exact spelling matters. An ordinary method such as take_damage has a name you choose.
A class is useful when it protects a coherent idea
Encapsulation means grouping related data and operations behind an interface that makes the intended usage clear. For a spaceship, requiring damage to be nonnegative is a rule about how its state may change. Centralizing the rule means every caller of that method benefits.
The earlier class still permits direct assignment such as first.health = -999. Sometimes public attributes are appropriate. Sometimes you want controlled access. A property lets attribute access invoke code, such as validation:
class Shield:
def __init__(self, strength):
self.strength = strength
@property
def strength(self):
return self._strength
@strength.setter
def strength(self, value):
if value < 0:
raise ValueError("Strength cannot be negative")
self._strength = value
shield = Shield(50)
shield.strength = 30
print(shield.strength)
A getter supplies a value when a property is read. A setter handles assignment. The @property decorator transforms the method definition into a managed attribute. A decorator applies another callable to a definition; a callable is something that can be called with parentheses. The @strength.setter line attaches the setter behavior.
_strength is the backing attribute holding the value. Its leading underscore signals internal use by convention; it does not prevent another programmer from accessing it. Assigning self.strength = value inside this setter would call the setter again, leading to recursive calls instead of storing the value. A property is an interface mechanism, not an access-control boundary against untrusted code.
Shared data and different kinds of method
An instance attribute such as self.health belongs to an instance. A class attribute is defined on the class and can be shared through class lookup. A mutable class-level list can therefore surprise you if you intended a fresh list per instance. Put self.inventory = [] in the initializer when each ship needs its own inventory.
A class method uses @classmethod and receives the class, conventionally named cls, rather than a particular instance. It is useful for alternative construction procedures. A static method uses @staticmethod and receives neither automatically; it can group a helper with the class when that placement is meaningful. Static methods are mentioned as further exploration in the course rather than developed as deeply as class methods.
The special method str supplies a readable string representation used by str(obj) and often printing. It returns a string; it should not merely print one. This is the same return-versus-side-effect distinction from Week 0, applied to objects.
Inheritance and operator behavior
Inheritance defines one class based on another. The base or parent class supplies behavior; the derived class or subclass reuses and extends it. For example, a specialized ship might begin from an existing ship class:
class Scout(Spaceship):
def __init__(self, name):
super().__init__(name)
self.speed = 12
scout = Scout("Swift")
scout.take_damage(5)
print(scout.name, scout.health, scout.speed)
This assumes the earlier Spaceship class. The output is Swift 95 12. super() gives access to the next applicable implementation in the method-resolution order; in this simple inheritance example, that is the base initializer. A method-resolution order is the order Python uses to look for inherited methods.
Inheritance is one design option. Composition is another: one object contains or refers to another, such as a ship having a shield. Prefer the relationship that fits the meaning. More inheritance does not automatically mean more professional code.
Operator overloading defines how operators behave for your class. add, for example, can define addition. You have already seen type-dependent operator behavior: numbers add numerically while strings concatenate. Custom operator behavior should remain unsurprising. A method call with a descriptive name is often clearer when an operation does not naturally resemble addition.
The lecture also revisits tuples: ordered collections whose element references cannot be replaced. A tuple containing a list can still refer to a list that changes. Immutable does not necessarily mean every reachable object is immutable.
Practice and answers
Question A: What does self refer to during second.take_damage(10)? Question B: Does assigning alias = first create another ship? Question C: Why is _strength used inside the property? Question D: Must every program use classes?
Answer A: the particular instance referenced by second. Answer B: no; it creates another name for the same instance, so changes through either name affect that object. Answer C: it stores the value without repeatedly invoking the property's setter. Answer D: no. Small functions and built-in collections are often sufficient. Use a class when grouping state and behavior clarifies the design.
Before watching, distinguish the class from the instance and the method from the call. Afterward, make two objects, change one, and inspect both. That experiment explains OOP more concretely than memorizing a list of design terms.
Sources: Harvard Week 8 notes, lecture transcript, classes tutorial, data model, and property, classmethod, and staticmethod.
Week 8 connections to full stack development
README connections: M4 React components, M5 state ownership, M11 models, M9 entities.
Organize information around a coherent responsibility
A Python object can hold the state and behavior of one spaceship. Later, a backend model describes a task or request, and a React component describes a piece of interface. These are useful comparisons in organizing responsibility, but a React component is not simply a Python class translated into JavaScript.
Ownership matters more than the number of classes
If two ships have independent health, changing one should not change the other. If two views display the same task, they need a clear owner for that shared data. M5 revisits these questions through lifting state, context, and state-management libraries.
Stored values and computed views are different
A completed-task total can be derived from the task list. A filtered view can be derived from the chosen filter and the records. Before adding another stored value, ask which operation keeps it consistent. This connects the Week 2 data sketch to React’s state model.
Use the smallest organization you can explain
Functions may remain sufficient for an isolated calculation. Classes help when data and operations form a coherent unit. Libraries such as Zustand and Redux introduce additional concepts, so choose them later in response to an actual application need rather than to complete a checklist.
Focused reading
Keep the CS50P class examples as the main work. Preview React’s thinking about components and Managing State only if the comparison is useful. Build the React tutorial after JavaScript, DOM, and event handling feel comfortable.
React guidance on choosing state structure
Try and explain
Draw two independent task objects and a separate list containing them. Identify which operation changes a task and which code computes the completed count. Explain what should be shared and what should remain independent.
Save for later
Complete M4 before the M5 libraries. Routing, fetching, server-state caching, and React effects require their own study; they are not prerequisites for passing this week.
README resources for this connection
Modules 4A 4B React js
Topics to study in this area: components, props, JSX, useState, useEffect, fetching APIs, React Router, TanStack Query.
react.dev Tic-Tac-Toe tutorial
"You Might Not Need an Effect"
Module 5 State Management Advanced React
Topics to study in this area: Context API, Zustand, Redux Toolkit, custom hooks, useMemo/useCallback/React.memo.
react.dev: Reusing Logic with Custom Hooks
Week 9: additional tools and Python fluency
Understand the problem each tool removes
Et cetera means 'and the rest.' This lecture introduces many features, which can feel overwhelming if you treat them as one memorization task. Group them by purpose: organize information, make interfaces explicit, transform collections, and produce results on demand. Every feature below builds on names, functions, loops, and objects you already know.
Fluency means selecting an appropriate expression of an idea. It does not mean making every line as short as possible or using every available feature. Keep a straightforward version beside a compact one until their equivalent behavior is clear.
Sets, globals, and constants
A set stores distinct hashable elements and is useful for membership tests and eliminating duplicates. Hashable means usable as a dictionary key or set element with a stable hash/equality relationship while stored there. Hashing computes a value used to organize fast lookup; you do not need its implementation to begin using sets. Integers and strings are common hashable elements; ordinary lists are not.
visited = ["Mars", "Earth", "Mars"]
unique = set(visited)
print(sorted(unique))
print("Mars" in unique)
The outputs are ['Earth', 'Mars'] and True. A set does not supply sequence indexing or a promised iteration order. sorted creates a sorted list for predictable presentation. {} creates an empty dictionary; use set() for an empty set.
A global variable is a name defined at module level. A function can often read it, but assigning to that name inside the function normally creates a local binding unless declared otherwise. global score directs assignments to the module-level name. This can be useful, but hidden shared updates make information flow harder to follow. Passing values and returning results often makes dependencies clearer.
A constant is a value intended to remain unchanged. MAX_HEALTH = 100 communicates that intent through an uppercase-name convention. Uppercase spelling does not stop Python from rebinding the name. The convention is guidance to readers, not runtime enforcement.
Type hints and documentation describe an interface
def add_bonus(score: int, bonus: int = 10) -> int:
"""Return a score with the specified bonus added."""
return score + bonus
print(add_bonus(40))
The colon after each parameter introduces a type annotation. The arrow describes the return type. The triple-quoted string at the beginning of the function is a docstring: documentation stored with the definition. It is different from an ordinary # comment, which is not stored as the function's documentation.
Type hints do not convert values or automatically enforce types at runtime. A static type checker such as mypy inspects code without executing its ordinary application behavior and reports some inconsistencies. Static means analysis of code rather than running it against actual inputs. A passing type check does not prove that calculations or business rules are correct; tests and reasoning remain necessary.
Good documentation explains accepted inputs, returned results, meaningful failures, and assumptions. You need not repeat every obvious line. Documentation tools can turn docstrings into reference pages, but readable documentation begins with useful sentences, not elaborate formatting.
Unpacking and flexible arguments
position = (100, 200)
x, y = position
print(x, y)
values = [40, 10]
print(add_bonus(*values))
named = {"score": 40, "bonus": 10}
print(add_bonus(**named))
The outputs are 100 200, 50, and 50. Unpacking distributes multiple items into separate names or arguments. In a call, *values supplies positional arguments from an iterable. **named supplies keyword arguments from a mapping with suitable string keys. A mapping associates keys with values; dictionaries are the usual example.
In a function definition, the stars collect rather than distribute:
def describe(*args, **kwargs):
print(args)
print(kwargs)
describe("Comet", 100, color="cyan")
The first output is the tuple ('Comet', 100); the second is the dictionary {'color': 'cyan'}. args and kwargs are conventional names, not required keywords. The star syntax determines their behavior. Prefer explicit named parameters when the function expects a small fixed set of inputs; flexible arguments should serve a real need.
Command-line tools can explain themselves
argparse is a standard-library module for interpreting command-line options. An option is a named setting supplied at launch, such as --lives 3. A parser interprets those tokens according to rules you register, can convert values, and generates help text.
The following example supplies demonstration arguments explicitly so it can run without terminal setup:
import argparse
parser = argparse.ArgumentParser(description="Space game options")
parser.add_argument("--lives", type=int, default=3)
options = parser.parse_args(["--lives", "5"])
print(options.lives)
It prints 5. In a real command-line script, parse_args() without that list reads the program's command-line arguments. type=int converts the supplied text but does not automatically impose a sensible range; validating that lives is positive remains your application's decision. --help provides usage information generated from the parser's configuration.
Comprehensions, map, and filter
Start with an ordinary loop:
scores = [0, 10, 20]
doubled = []
for score in scores:
if score > 0:
doubled.append(score * 2)
print(doubled)
A list comprehension can express the same operation:
scores = [0, 10, 20]
doubled = [score * 2 for score in scores if score > 0]
print(doubled)
Both print [20, 40]. Read the comprehension in this order: visit each score, retain it if positive, then produce the doubled value. The written expression appears first, which can be unfamiliar initially. A comprehension builds a collection; the example does not modify scores.
A dictionary comprehension can construct associations, such as {name: 0 for name in ["Comet", "Nova"]}. map(function, items) supplies transformed items as requested. filter(function, items) supplies items for which the function's result is truthy. They return iterators in Python 3, rather than automatically producing lists. Functions can themselves be passed as values; this is one idea associated with functional programming, an approach emphasizing composition of functions and transformations.
Choose the version that expresses the task clearly. A nested comprehension with several conditions may be harder to understand than a few ordinary statements. Concise syntax does not remove the underlying repeated work.
Enumerate: get the item and its counter together
pilots = ["Tommy", "Nova"]
for rank, pilot in enumerate(pilots, start=1):
print(rank, pilot)
The outputs are 1 Tommy and 2 Nova. enumerate pairs a counter with each item. The default counter begins at zero; start=1 suits a displayed ranking. This avoids manually updating a separate counter when the relationship is already built into the operation.
Generators: calculate when the caller asks
An iterable can supply items for iteration. An iterator maintains progress and provides successive items. A generator is a particular kind of iterator, often created by a function containing yield. Lazy evaluation means delaying work until its result is requested.
def countdown(start):
while start > 0:
yield start
start -= 1
counter = countdown(3)
print(next(counter))
print(next(counter))
print(list(counter))
print(list(counter))
The outputs are 3, 2, [1], and []. Calling countdown(3) returns a generator without running its body immediately. next asks for the next value. At yield, the generator supplies that value and pauses, retaining its local state. The next request resumes after the yield. Once exhausted, the same iterator does not start over; create a new generator for another traversal.
Unlike return, yield can let the function continue producing values later. A loop knows how to consume an iterator until exhaustion. Asking next after exhaustion raises StopIteration, which normal for iteration handles as its stopping signal.
A generator can avoid keeping every produced result in memory at once. It does not guarantee small memory usage if each result is enormous or if you immediately collect everything with list(). Time and space complexity describe how work and memory grow with input size. Laziness changes when work happens and what must be retained, not necessarily the total work needed.
Recursion as further exploration
The Week 9 page also links a short on recursion: a function solving a problem by calling itself on a simpler case. A base case supplies a stopping result; a recursive case reduces the problem toward that base case. Without progress and a stopping condition, calls can continue until Python raises a recursion-depth error. This is a separate technique to explore after ordinary functions and loops feel comfortable.
Practice and answers
Question A: Does count: int = "three" automatically turn the text into an integer? Question B: What does a second traversal of an exhausted generator produce? Question C: Is {} an empty set? Question D: When would an ordinary loop be preferable to a comprehension?
Answer A: no; annotations neither convert nor automatically reject that assignment at runtime. A type checker can report the inconsistency. Answer B: no further values from that same generator. Answer C: no, it is an empty dictionary. Answer D: when multiple steps, side effects, or complicated conditions make the loop easier to read and debug.
Before watching, group unfamiliar tools by purpose. Afterward, rewrite one familiar loop as a comprehension, then decide which version you would keep and why. Create a generator and request values manually so that pausing, resuming, and exhaustion become observable rather than abstract terms.
Sources: Harvard Week 9 notes, lecture transcript, Week 9 and recursion short, typing, mypy, argparse, built-ins, generators, and glossary.
Week 9 connections to full stack development
README connections: M7 TypeScript, M11 Pydantic, M12 Next.js, M13 and M14 AI, M16 operations.
Types describe interfaces and validation checks actual data
Python type annotations help readers and analysis tools understand expected values. TypeScript brings a type system to JavaScript development in M7. A type description does not make external input trustworthy by itself. M11 Pydantic and M7 Zod provide tools for checking data at runtime, when the program is executing.
Data transformations recur across stacks
Comprehensions, map, filter, unpacking, and generators give you ways to transform or consume data. Later, a frontend can filter tasks and a backend can process records. Explain the input, output, and ordering before preferring a compact expression. A Python generator is useful preparation for thinking about incremental consumption, but does not itself teach network streaming.
An AI application still needs ordinary engineering
M13 introduces AI concepts and M14 builds features using them. Use an explicit input and output contract, inspect failures, and check generated results before relying on them. M15 then adds retrieval. A sensible learning progression is an ordinary API, a small generated response, conversation history, then retrieval only when needed.
A web framework adds another execution model
M12 Next.js introduces routing and server/client responsibilities on top of React. Learn where code runs and who owns the data before implementing a full stack page. This extends earlier client/server ideas but cannot be compressed into CS50P’s final lecture.
Delivery makes the program reproducible for someone else
M16 concerns environments, automation, and deployment. Start by documenting how to install dependencies, run the program, and run its tests. Docker, CI, hosting, and monitoring are later tools for making those steps repeatable and observing a running service.
Focused reading
Finish the CS50P chapter first. Revisit the Python Tutorial and Pydantic links from Week 4. Read the TypeScript introductory material as a comparison after JavaScript. The Next.js, AI, and operations catalogs below are post-CS50P destinations, not a Week 9 workload.
Python type hints and runtime limits
Try and explain
Write the interface for one function in plain English: inputs, result, invalid inputs, and side effects. Add annotations you understand, then explain which checks still need to run on external data. Describe one dataset transformation before choosing a loop or comprehension.
Save for later
Complete the CS50P final project next. Follow the extension order at the end of this document before attempting the README capstone.
README resources for this connection
Module 7 TypeScript
Topics to study in this area: static typing, inference, interfaces, types, enums, generics, type-safe API calls, Zod.
Total TypeScript — free tutorials
Module 12 Next js
Topics to study in this area: App Router, server vs client components, SSR/SSG/ISR, Server Actions, API routes, Auth.js, Vercel + Supabase deployment.
Module 13 AI Fundamentals for Developers
Topics to study in this area: tokens, context windows, temperature, production prompting, system prompts, structured outputs, responsible AI.
Anthropic Docs — Build with Claude
Anthropic Prompt Engineering Guide
Module 14 Building AI Powered Features
Topics to study in this area: OpenAI API auth/requests, streaming with Vercel AI SDK, conversation memory, content generation, smart search.
Module 16 DevOps Cloud CI CD
After the lectures: combine the ideas
A project grows through understandable versions
You can build an exciting project without waiting until every advanced feature feels natural. Begin with the smallest visible behavior and add one responsibility at a time. For a space-dodging game, a possible sequence is a window, a movable shape, one obstacle, several obstacles, collision handling, score, restart, then visual effects.
Each stage should have a plain-language requirement. For example: 'Pressing left moves the ship toward zero without crossing the boundary.' Next write a function for the rule, test it, and connect it to input and drawing. This keeps the visual feedback while preserving code you can understand.
| Game responsibility | Useful course ideas | Example evidence of understanding |
|---|---|---|
| Current health and position | Variables, types | Explain a value before and after movement |
| Launch and game-over rules | Conditions | Correctly handle exact boundaries |
| Several moving obstacles | Lists, loops | Explain which object changes per iteration |
| Invalid settings | Exceptions, validation | Produce an understandable recovery path |
| Graphics and sound | Libraries | Explain imports and function arguments |
| Scoring correctness | Functions, tests | Check independent expected results |
| Saved progress | Files, conversion | Restore values after a fresh run |
| Text formatting rules | Regex when useful | Show accepted and rejected examples |
| Per-ship behavior | Classes when helpful | Modify one ship without changing another |
| Clear interfaces | Hints, docstrings | Explain inputs, output, and assumptions |
You need not use every feature. Regex may be irrelevant to your first game. A function may be clearer than a class for a simple calculation. A well-chosen omission is part of design judgment.
The official final project
Harvard's specification allows a Python project of your choosing within its requirements. At the time this guide was prepared, it required a main function in project.py, at least three additional top-level functions there, and pytest tests for at least three of those additional functions in test_project.py. It also specifies a dependency list, a README, and a demonstration video. A README is the introductory project documentation; a dependency list records external packages required to run it.
Treat that paragraph as orientation, not a substitute for the full submission instructions. Recheck the official page when you reach the project. Keep testable game rules separate from the continuously running graphical loop so they can be checked without requiring someone to play the game.
Source: Harvard final-project specification.
A debugging routine you can reuse
Start by stating the expected behavior and observed behavior separately. Find a small input that reproduces the difference. A reproducible example is one that another run or person can use to observe the same issue. Inspect the first point where actual values diverge from your expectation.
Use a trace table on paper or temporary prints. Later, use a debugger: a tool that pauses execution and lets you inspect values. A breakpoint marks a place to pause. Stepping means advancing execution in small increments. These are tools for observing behavior, not replacements for deciding what should happen.
Make one targeted change and repeat the example. When appropriate, add an automated check so the behavior stays correct. Do not change five unrelated lines and then guess which change mattered. Avoid treating an error message as a verdict about your ability; it is evidence about this execution.
What to remember and what to look up
Remember recurring ideas: assignment binds a name, a call runs behavior, return hands back a result, conditions select paths, loops repeat, collections organize, and exceptions signal failed operations. Practice recognizing those patterns in unfamiliar examples.
Look up library names, optional arguments, file-format details, uncommon regex syntax, and exact tool commands. The useful skill is knowing what question to ask and being able to interpret the answer. Reading documentation means checking input types, return behavior, side effects, examples, and important exceptions.
Use AI as a tutor by requesting a hint, a trace, a counterexample, or an explanation of one unfamiliar symbol. A counterexample is an input that disproves a proposed general claim. After receiving help, rebuild a small version and explain it without the answer in view. This is a practice recommendation, not a promise that one study routine fits everyone.
A reusable before-and-after lecture worksheet
Before the lecture: What problem does this week solve? Which two terms are unfamiliar? What output do I predict from one example? What is my reason for that prediction?
During the lecture: What changed between two versions? Was the change about correctness, readability, reuse, or resource cost? Which line or term caused confusion? What input would distinguish the old and new behavior?
After the lecture: Can I rebuild one small example? Can I explain its values over time? Can I handle an empty, zero, boundary, or invalid input when relevant? Can I connect the concept to one feature I want to build?
Move forward when you can explain and modify a small example with references available. You do not need perfect recall of the chapter's entire vocabulary. Revisit a concept when a later task makes its purpose clearer.
Quick reference: confusing pairs
| Pair | Distinction |
|---|---|
| = / == | Assign a value / compare values |
| Parameter / argument | Input name in definition / supplied value in call |
| Define / call | Establish behavior / execute it |
| Print / return | Display output / send a value to the caller |
| Name / string | Refer to a value / literal text in quotes |
| Conversion / validation | Change representation or type / check allowed rules |
| if / while | Choose once when reached / repeat while condition holds |
| break / return | Exit nearest loop / exit function call |
| List index / dictionary key | Sequence position / lookup label |
| Alias / copy | Another name for same object / new container or object |
| Install / import | Make code available in environment / access module names |
| JSON / dictionary | Text interchange format / Python mapping object |
| Assertion / validation | Development or test claim / required runtime input check |
| Memory / saved file | Working program state / retained representation |
| Class / instance | Define a kind / create a particular object |
| Attribute / method | Named data or behavior / callable behavior on class or object |
| Annotation / conversion | Describe a type / produce a converted value |
| Iterable / iterator | Can supply iteration / tracks progress through items |
| return / yield | Finish a call / supply and pause a generator |
| Refactoring / optimization | Reorganize with same behavior / improve resource cost |
The table is a memory aid; the chapters explain where simplifications have limits. Keep it beside the lecture and use the worked examples when two terms still feel interchangeable.
Primary course reference: Harvard CS50P. Language reference: Python documentation. Prepared September 2026 using the official course sequence and current documentation available during this conversation.
After CS50P extend the project toward the web
Complete the official Python final project first. The README’s six projects are a separate progression for learning web development. A Python program can supply familiar rules and sample data, but translating it into JavaScript and adding a browser or server introduces new work. Reuse your understanding rather than assuming the tools are interchangeable.
Official CS50P final project requirements
The final-project README mentioned in the CS50P companion is documentation for your own Python project. The supplied fullstack-ai-free README is a curriculum. They serve different purposes.
First build a browser interface
Complete M2 HTML and CSS, then M3 JavaScript including DOM and events. Use the M1 workflow throughout. Project 1 is the vanilla JavaScript shopping cart or quiz. Draw on Weeks 0–3 for functions, decisions, collections, and invalid-input behavior. Verify: explain an input-to-result path, handle a boundary case, and rebuild a small feature without copying a solution.
Then learn React and shared state
Complete M4 and build Project 2, the React Task and Productivity Manager, including the README’s multi-page and server-state goals. Study M5 state ownership and M6 interface design, then M7 TypeScript. Project 3 is the React and TypeScript e-commerce interface with cart state. Draw on Weeks 2, 7, 8, and 9. Verify: show which data is stored and which is derived, preserve valid user work through errors, and explain what each type check can establish.
Add a backend and persistent records
Complete M8 Node and Express, M9 PostgreSQL and Prisma, then M10 authentication, security, and payment study. Project 4 is the local business recommendation platform with its API, database, and authentication. Weeks 3–7 provide useful foundations. Verify: test invalid requests and forbidden access, save and reload records, and explain record relationships. Use payment test environments while learning.
Revisit Python through a web API
Complete the FastAPI and Pydantic portion of M11 after the Python course. Your CS50P learning supplies the Python fundamentals; routing, HTTP, request models, and React integration remain new. Verify: send a request from a client, inspect a successful response and an invalid-input response, and explain the boundary between browser and server. This is an additional perspective on the Express work rather than a prerequisite for the original README’s first three projects.
Introduce AI features after ordinary APIs
Study M12 Next.js after React and TypeScript; study M13 before M14. Project 5 is the AI customer-support chat with streaming and conversation history. Add M15 retrieval after understanding basic AI calls and stored data. Weeks 4, 6, and 9 supply conceptual foundations. Verify: explain the data sent to the service, handle a failed call, inspect history behavior, and evaluate retrieval against several questions with known relevant passages.
Finish with delivery and a capstone
Apply M16 operations and M17 testing throughout the project work, then finish M18. Project 6 is the Next.js and Supabase capstone with a RAG knowledge-base chatbot, tests, CI, monitoring, and deployment. Week 5’s checks and Week 9’s reproducibility habits remain useful. Verify: a fresh setup can run the app and its checks, the deployed user path works, and failures have a useful recovery path.
Module 18 portfolio and project documentation
For each completed web project, document its purpose, screenshot or demonstration, technology choices, setup, and architecture. Include an example environment file with placeholders, meaningful tests and commit history, and a working demo when appropriate. Explain what you built and can maintain. Keep real credentials out of published files.
The README names Vercel, Supabase, Render or Fly.io, Sentry, and GitHub Actions as deployment or operations options. Choose and verify current plans when a project is ready to deploy; a resource listing is not a guarantee of free hosting or zero API cost.
Alternative web curricula to consult after CS50P
The README also lists structured alternatives. Use one only when it helps fill the next prerequisite; avoid starting several complete curricula at once. CS50P remains the course sequence for this companion.
Where to find each README module
Module 1: Developer Mindset & AI-Powered Workflow — Week 0 full stack resource catalog.
Module 2: Web Foundations — HTML, CSS & Responsive Design — Week 0 full stack resource catalog.
Module 3: JavaScript Essentials & Modern ES6+ — Week 0 full stack resource catalog.
Modules 4A/4B: React.js — Week 8 full stack resource catalog.
Module 5: State Management & Advanced React — Week 8 full stack resource catalog.
Module 6: UI/UX Principles & AI-Assisted Design — Week 7 full stack resource catalog.
Module 7: TypeScript — Week 9 full stack resource catalog.
Module 8: Node.js & Express — Week 4 full stack resource catalog.
Module 9: Databases — SQL & NoSQL — Week 6 full stack resource catalog.
Module 10: Authentication, Security & Payments — Week 3 full stack resource catalog.
Module 11: Python for Backend & Data APIs — Week 4 full stack resource catalog.
Module 12: Next.js — Week 9 full stack resource catalog.
Module 13: AI Fundamentals for Developers — Week 9 full stack resource catalog.
Module 14: Building AI-Powered Features — Week 9 full stack resource catalog.
Modules 15A/15B: Vector Databases & RAG — Week 6 full stack resource catalog.
Module 16: DevOps, Cloud & CI/CD — Week 9 full stack resource catalog.
Module 17: Testing, Code Quality & System Design — Week 5 full stack resource catalog.
Module 18: Capstone Project & Portfolio — After CS50P project and portfolio section.
Source documents
Python teaching chapters: CS50P_Reading_Companion.docx. Supplemental module topics, resource links, and six-project sequence: fullstack-ai-free/README.md. The weekly connections and study order combine these materials into a CS50P-led reading path.