learn

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.

Adapted from CS50PCC BY-NC-SA 4.0Weeks 0–9

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.

LectureCore capabilityExample use
0Name values and define actionsStore health and calculate a bonus
1Make decisionsChoose a warning or game-over message
2Repeat and organize workUpdate a collection of enemies
3Handle failed operationsRecover from invalid numeric input
4Reuse librariesChoose random positions and access services
5Test behaviorCheck scoring and movement rules
6Read and write filesSave scores and process images
7Describe text patternsValidate a player-name format
8Organize objectsGive each spaceship its own state
9Use additional Python toolsClarify 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 instructionhealthdamage
health = 100100Not yet defined
damage = 2510025
health = health - damage7525

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.

OperatorNumeric exampleResult
+7 + 29
-7 - 25
*7 * 214
/7 / 23.5
//7 // 23
%7 % 21
**7 ** 249

// 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.

Official CS50P Week 0

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.

MDN: Web standards

Anthropic Prompt Engineering Guide

OpenAI Prompt Engineering Guide

Cursor Docs

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

MDN Learn: CSS styling basics

Flexbox Froggy

Grid Garden

CSS-Tricks: Complete Guide to Flexbox

Tailwind CSS Docs

web.dev: Learn Responsive Design

Module 3 JavaScript Essentials Modern ES6

Topics to study in this area: syntax, functions, DOM manipulation, events, interactive UI.

javascript.info

MDN Learn: JavaScript

Eloquent JavaScript

JavaScript30

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.

healthhealth <= 0Next relevant checkFinal status
-5TrueNoneGame over
0TrueNoneGame over
20Falsehealth < 30 is TrueLow health
30Falsehealth < 30 is FalseReady

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.

ABA and BA or B
FalseFalseFalseFalse
FalseTrueFalseTrue
TrueFalseFalseTrue
TrueTrueTrueTrue

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.

Official CS50P Week 1

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 checkConditionActionValue afterward
3TruePrint 3, subtract 12
2TruePrint 2, subtract 11
1TruePrint 1, subtract 10
0FalseLeave the loop0

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.

Official CS50P Week 2

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

KindWhat happensExample
Syntax errorPython cannot parse the instructionsA missing closing quote
Runtime exceptionExecution reaches an operation that failsint("comet")
Logic errorExecution completes with wrong behaviorAdding 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 entersConversionRange checkResult
cometRaises ValueErrorNot reachedExplain, ask again
99SucceedsFalseExplain, ask again
2SucceedsTrueReturn 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.

Official CS50P Week 3

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.

FastAPI handling errors

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.

OWASP Top 10

OWASP Cheat Sheet Series

jwt.io Introduction

Auth.js Docs

Stripe Docs + test mode

PayMongo Docs

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.

Official CS50P Week 4

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 first steps

FastAPI request bodies and validation

FastAPI handling errors

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.

Node.js Learn

Express Guide

MDN: Express/Node tutorial

MDN: HTTP status codes

MDN: CORS

OpenAPI Specification

Module 11 Python for Backend Data APIs

Topics to study in this area: Python fundamentals, FastAPI, Pydantic, CORS, React + FastAPI integration.

Official Python Tutorial

Automate the Boring Stuff

FastAPI Tutorial

Pydantic Docs

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 inputExpected behaviorReason
position 20, distance 5Return 15Ordinary movement
position 20, distance 20Return 0Exactly reach boundary
position 20, distance 25Return 0Clamp at boundary
position 0, distance 5Return 0Already at boundary
position 20, distance -1Raise ValueErrorReject 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.

Official CS50P Week 5

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.

pytest getting started

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

Vitest Docs

Testing Library

Playwright Docs

ESLint

Prettier

Supertest

Sentry

System Design Primer

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.

ModeMeaningExisting content
rRead textPreserved
wWrite textReplaced
aAppend textPreserved, new text added
rbRead bytesPreserved

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.

Official CS50P Week 6

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.

PostgreSQL Tutorial

Official PostgreSQL Tutorial

SQLBolt

Prisma — Get Started

dbdiagram.io

Use The Index, Luke

Modules 15A 15B Vector Databases RAG

Topics to study in this area: embeddings, Supabase pgvector, chunking, RAG architecture, knowledge-base chatbot.

Supabase AI & Vectors docs

pgvector README

Anthropic Cookbook — RAG recipes

Contextual Retrieval

LangChain RAG tutorial

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 componentDefault meaningExample
[0-9]One digit from 0 to 9Matches the 7 in 7
.Any one character except newlinea.c matches abc
*Zero or more repetitionsab* matches a or abbb
+One or more repetitionsab+ requires at least one b
?Zero or one repetitionab? matches a or ab
{m,n}Between m and n repetitions[0-9]{2,4}
(...)Capture a matching group([0-9]+)
.A literal dotA 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.

Official CS50P Week 7

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.

Refactoring UI blog

Figma Learn

shadcn/ui Docs

Radix Primitives

React Hook Form

v0.dev

Laws of UX

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 instructionMeaning
SpaceshipThe class object
Spaceship("Comet")Create and initialize an instance
self.name = nameStore this call's name on this instance
first.healthRead 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.

Official CS50P Week 8

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/learn

react.dev Tic-Tac-Toe tutorial

"You Might Not Need an Effect"

React Router Docs

TanStack Query Docs

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: Managing State

react.dev: Reusing Logic with Custom Hooks

Zustand Docs

Redux Toolkit "Essentials"

useMemo

useCallback

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.

Official CS50P Week 9

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.

TypeScript Handbook

Total TypeScript — free tutorials

React TypeScript Cheatsheet

Zod Docs

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.

Next.js Learn course

Next.js Docs

Vercel

Supabase Docs

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

OpenAI Docs

Anthropic Courses (GitHub)

Anthropic Cookbook

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.

Vercel AI SDK Docs

Anthropic Messages API

OpenAI API Reference

Ollama

Module 16 DevOps Cloud CI CD

GitHub Actions Docs

GitHub Skills

Docker Get Started

Play with Docker

12 Factor App

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 responsibilityUseful course ideasExample evidence of understanding
Current health and positionVariables, typesExplain a value before and after movement
Launch and game-over rulesConditionsCorrectly handle exact boundaries
Several moving obstaclesLists, loopsExplain which object changes per iteration
Invalid settingsExceptions, validationProduce an understandable recovery path
Graphics and soundLibrariesExplain imports and function arguments
Scoring correctnessFunctions, testsCheck independent expected results
Saved progressFiles, conversionRestore values after a fresh run
Text formatting rulesRegex when usefulShow accepted and rejected examples
Per-ship behaviorClasses when helpfulModify one ship without changing another
Clear interfacesHints, docstringsExplain 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

PairDistinction
= / ==Assign a value / compare values
Parameter / argumentInput name in definition / supplied value in call
Define / callEstablish behavior / execute it
Print / returnDisplay output / send a value to the caller
Name / stringRefer to a value / literal text in quotes
Conversion / validationChange representation or type / check allowed rules
if / whileChoose once when reached / repeat while condition holds
break / returnExit nearest loop / exit function call
List index / dictionary keySequence position / lookup label
Alias / copyAnother name for same object / new container or object
Install / importMake code available in environment / access module names
JSON / dictionaryText interchange format / Python mapping object
Assertion / validationDevelopment or test claim / required runtime input check
Memory / saved fileWorking program state / retained representation
Class / instanceDefine a kind / create a particular object
Attribute / methodNamed data or behavior / callable behavior on class or object
Annotation / conversionDescribe a type / produce a converted value
Iterable / iteratorCan supply iteration / tracks progress through items
return / yieldFinish a call / supply and pause a generator
Refactoring / optimizationReorganize 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.

Full Stack Open

The Odin Project

freeCodeCamp

CS50x

CS50 Web

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.

Harvard CS50P official course sequence