21 min read

> "The question of whether a computer can think is no more interesting than the question of whether a submarine can swim."

Learning Objectives

  • Create variables and assign values of different types (int, float, str, bool)
  • Evaluate arithmetic, string, and comparison expressions
  • Convert between types using type casting functions
  • Use f-strings to format output with variables and format specifiers
  • Explain how Python manages variables in memory (names as labels, not boxes)

Chapter 3: Variables, Types, and Expressions: The Building Blocks

"The question of whether a computer can think is no more interesting than the question of whether a submarine can swim." — Edsger W. Dijkstra

Chapter Overview

In Chapter 2, you wrote programs that printed messages and took input from the user — but everything was fleeting. You couldn't hold onto a value, compute with it, or combine it with other values. That changes now.

Variables, types, and expressions are the fundamental building blocks of every program you'll ever write. A variable lets you give a name to a piece of data. A type tells Python what kind of data it is — a number? Text? A yes/no flag? An expression lets you combine values and variables into new values: adding numbers, joining strings, comparing scores to a threshold.

Here's what's different about this chapter from most CS1 textbooks: we're going to get the mental model right from the start. Most beginners think of a variable as a box that holds a value. That metaphor is intuitive but wrong — and it will mislead you later when things get more interesting. In Python, a variable is a name tag attached to an object in memory. Getting this right now saves you hours of confusion in Chapters 8, 14, and beyond.

By the end of this chapter, you'll have the vocabulary and tools to build programs that actually compute things — and you'll add timestamp-tracked tasks to your TaskFlow project.

In this chapter, you will learn to: - Create variables and assign values of different types (int, float, str, bool) - Evaluate arithmetic, string, and comparison expressions - Convert between types using type casting functions - Use f-strings to format output with variables and format specifiers - Explain how Python manages variables in memory (names as labels, not boxes)

🏃 Fast Track: If you've programmed in another language and just need the Python syntax, skim 3.1-3.2, focus on 3.8 (f-strings are Python-specific and excellent), and read 3.10 (Python's memory model differs from C/Java). Then do the project checkpoint.

🔬 Deep Dive: Section 3.10 on Python's memory model rewards careful reading. If you want to go further, try running id() on various objects in the REPL and experiment with what happens when you rebind names.


3.1 What Are Variables?

Let's start with a program that has a problem:

print(95)
print(87)
print(91)
print("Average:", (95 + 87 + 91) / 3)
95
87
91
Average: 91.0

This works, but it's fragile. What if you need to change one of the scores? You'd have to change it in two places — once in the print() call and once in the average calculation. And what do those numbers even mean? Is 95 a temperature? A test score? A heart rate?

Variables solve both problems. A variable gives a meaningful name to a value so you can use it, reuse it, and modify it in one place.

score_1 = 95
score_2 = 87
score_3 = 91
average = (score_1 + score_2 + score_3) / 3

print(score_1)
print(score_2)
print(score_3)
print("Average:", average)
95
87
91
Average: 91.0

Now the code is self-documenting. Anyone reading it can tell these are scores, and if you need to change score_2, you change it in one place and the average updates automatically.

3.1.1 Assignment: The = Operator

The = sign in Python is the assignment operator. It does not mean "equals" in the mathematical sense. It means "make this name point to this value."

student_name = "Elena"

Read this as: "the name student_name now refers to the string object "Elena"."

Here's something that would be nonsense in math but makes perfect sense in Python:

counter = 0
counter = counter + 1
print(counter)
1

In math, counter = counter + 1 has no solution. In Python, it means: "Take the current value of counter (which is 0), add 1 to get 1, and make counter point to that new value."

3.1.2 The Name Tag Model

🚪 Threshold Concept: Variables as Name Tags, Not Boxes

Most beginners imagine a variable as a box that holds a value — like a physical container labeled x with the number 42 sitting inside it.

This mental model is wrong. In Python, a variable is a name tag (or label, or sticky note) attached to an object that lives somewhere in memory.

Before this clicks: "The variable x IS the number 42."

After this clicks: "The variable x POINTS TO an object that happens to be 42 right now."

Why does this matter? Because two name tags can point to the same object:

python a = 42 b = a

In the "box" model, this would mean copying 42 into a second box. But that's not what happens. Both a and b are name tags attached to the same integer object 42 in memory. There's only one 42. Two names point to it.

We'll prove this in section 3.10 using id(). For now, just start building the habit of thinking of variables as name tags pointing to objects, not boxes holding values.

Here's a critical consequence of the name tag model:

a = 42
b = a       # b now points to the same 42 that a points to
a = 99      # a gets a NEW name tag — it now points to 99

print(a)    # 99
print(b)    # still 42!
99
42

When we wrote a = 99, we didn't change the object 42. We moved the a name tag from the 42 object to a new 99 object. The b name tag is still stuck to 42, unaffected.

🧩 Productive Struggle: Before reading on, predict the output of this code. Write down your answer, then check it:

python x = 5 y = x x = 10 print(y)

Answer

y is 5. When you wrote y = x, you made y point to the same object as x (which was 5). When you then wrote x = 10, you moved the x name tag to a new object (10). The y name tag didn't move — it's still attached to 5.

If you said 10, you were using the "box" model. In the box model, y = x would make y somehow track x. But name tags don't work that way. Each name independently points to an object.


3.2 Naming Variables

Python has rules about what you can name a variable, and conventions about what you should name it.

3.2.1 The Rules (Python enforces these)

  1. Names can contain letters, digits, and underscores: score_1, total_count, x2
  2. Names cannot start with a digit: 1st_place is illegal; first_place is fine
  3. Names are case-sensitive: Score, score, and SCORE are three different variables
  4. Names cannot be Python keywords (reserved words): if, while, for, class, return, True, False, None, etc.
# Legal names
student_name = "Elena"
score_1 = 95
_internal = "private by convention"
totalCount = 42

# Illegal names — these cause SyntaxError
# 1st_place = "Gold"     # starts with a digit
# my-variable = 10       # hyphens aren't allowed
# class = "Biology"      # 'class' is a keyword

3.2.2 The Conventions (community standards you should follow)

Python developers follow a set of conventions documented in PEP 8, Python's official style guide:

  • Use snake_case for variable names: student_name, not studentName
  • Use all caps for constants: MAX_RETRIES = 3, PI = 3.14159
  • Choose meaningful names: average_score beats a, and temperature_celsius beats t
  • Keep names reasonably short but descriptive: n is fine in a loop, but number_of_students_enrolled_this_semester is too long

✅ Best Practice: A good variable name answers the question "what is this?" without needing a comment. If you find yourself writing x = 95 # student's midterm score, just name it midterm_score instead.

Here's the grade calculator from Chapter 1's concept introduction, now implemented with meaningful variable names:

student_name = "Elena"
midterm_score = 87
final_score = 93
homework_average = 91

overall_average = (midterm_score + final_score + homework_average) / 3
print(f"{student_name}'s average: {overall_average}")
Elena's average: 90.33333333333333

(We'll clean up that long decimal in section 3.8 when we learn f-string formatting.)


3.3 Data Types: int, float, str, bool

Every value in Python has a type — a category that determines what kind of data it is and what operations you can perform on it. Python has many types, but you'll work with four fundamental ones constantly.

3.3.1 Integers (int)

An integer is a whole number — no decimal point. Positive, negative, or zero.

students = 35
temperature = -12
year = 2025
zero = 0

print(type(students))
print(type(temperature))
<class 'int'>
<class 'int'>

The type() function tells you what type a value is. You'll use it constantly while learning. Python integers have no maximum size — they can be as large as your computer's memory allows.

big_number = 10 ** 100   # 10 to the power of 100 — a googol
print(big_number)
print(type(big_number))
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
<class 'int'>

3.3.2 Floating-Point Numbers (float)

A float (short for floating-point number) is a number with a decimal point.

gpa = 3.85
pi = 3.14159
negative_temp = -40.0
exactly_one = 1.0         # this is a float, not an int!

print(type(gpa))
print(type(exactly_one))
<class 'float'>
<class 'float'>

⚠️ Pitfall: Floating-point arithmetic can produce surprising results:

python print(0.1 + 0.2)

0.30000000000000004

This isn't a Python bug — it's a fundamental limitation of how computers represent decimal numbers in binary. For now, just be aware of it. When comparing floats, don't use ==; instead, check whether they're "close enough." We'll revisit this in later chapters.

3.3.3 Strings (str)

A string is a sequence of characters — text. You create strings with single quotes, double quotes, or triple quotes:

greeting = "Hello, world!"
name = 'Elena'
multiline = """This string
spans multiple
lines."""

print(type(greeting))
print(greeting)
print(multiline)
<class 'str'>
Hello, world!
This string
spans multiple
lines.

Use double quotes or single quotes — pick one style and be consistent. Triple quotes are useful for text that spans multiple lines.

💡 Intuition: Strings are for text. Even if the text happens to contain digits, it's still text:

```python age_as_string = "25" age_as_int = 25

print(type(age_as_string)) # print(type(age_as_int)) # ```

The string "25" and the integer 25 are completely different objects. You can do math with the integer. You can't do math with the string (at least, not the way you'd expect).

3.3.4 Booleans (bool)

A boolean is either True or False. That's it. Two possible values.

is_enrolled = True
has_prerequisite = False

print(type(is_enrolled))
print(is_enrolled)
<class 'bool'>
True

Note the capitalization: True and False, not true and false. Python is picky about this.

Booleans become essential in Chapter 4 when we start making decisions with if statements. For now, just know they exist and that comparison expressions produce them (section 3.6).

3.3.5 Type Comparison Table

Type Python Name Example Values What It Represents
Integer int 42, -7, 0, 10**100 Whole numbers
Float float 3.14, -0.001, 1.0 Numbers with decimal points
String str "hello", 'CS 101', "" Text (sequence of characters)
Boolean bool True, False Logical values (yes/no, on/off)

🔄 Check Your Understanding (try to answer without scrolling up)

  1. What does type(3.0) return?
  2. Is "42" the same type as 42?
  3. What are the only two possible values of a boolean?

Verify

  1. <class 'float'> — the decimal point makes it a float, even though 3.0 is mathematically a whole number.
  2. No. "42" is a str (string), and 42 is an int (integer). They look similar but are fundamentally different types.
  3. True and False (capitalized).

3.4 Arithmetic Expressions

An expression is any combination of values, variables, and operators that Python can evaluate to produce a result. Arithmetic expressions work with numbers.

3.4.1 The Arithmetic Operators

a = 17
b = 5

print("Addition:       ", a + b)    # 22
print("Subtraction:    ", a - b)    # 12
print("Multiplication: ", a * b)    # 85
print("Division:       ", a / b)    # 3.4
print("Floor division: ", a // b)   # 3
print("Modulo:         ", a % b)    # 2
print("Exponentiation: ", a ** b)   # 1419857
Addition:        22
Subtraction:     12
Multiplication:  85
Division:        3.4
Floor division:  3
Modulo:          2
Exponentiation:  1419857
Operator Name Example Result Notes
+ Addition 17 + 5 22
- Subtraction 17 - 5 12
* Multiplication 17 * 5 85
/ Division 17 / 5 3.4 Always returns a float
// Floor division 17 // 5 3 Rounds down to nearest integer
% Modulo 17 % 5 2 Remainder after division
** Exponentiation 2 ** 10 1024 Raises to a power

3.4.2 Division: / vs //

This trips up beginners. Python has two division operators:

  • / is true division — it always gives you a float, even when the result is a whole number.
  • // is floor division — it drops the decimal part (rounds toward negative infinity).
print(10 / 3)    # 3.3333333333333335
print(10 // 3)   # 3
print(10 / 2)    # 5.0 — still a float!
print(10 // 2)   # 5   — an integer
3.3333333333333335
3
5.0
5

The modulo operator % gives you the remainder, which pairs naturally with floor division:

minutes = 135
hours = minutes // 60
remaining_minutes = minutes % 60
print(f"{minutes} minutes = {hours} hours and {remaining_minutes} minutes")
135 minutes = 2 hours and 15 minutes

3.4.3 Operator Precedence

Python follows standard mathematical order of operations (PEMDAS/BODMAS):

  1. ** (exponentiation) — highest
  2. Unary - (negation)
  3. *, /, //, % (multiplication, division, floor division, modulo)
  4. +, - (addition, subtraction) — lowest
result = 2 + 3 * 4      # 14, not 20 — multiplication first
print(result)

result = (2 + 3) * 4    # 20 — parentheses override precedence
print(result)

result = 2 ** 3 ** 2     # 512, not 64 — ** is right-associative: 2 ** (3 ** 2) = 2 ** 9
print(result)
14
20
512

✅ Best Practice: When in doubt, use parentheses. (2 + 3) * 4 is clearer than relying on precedence rules, even if you know them by heart. Readability beats cleverness.

3.4.4 The Grade Calculator with Expressions

Let's build on our grade calculator. Elena's course has a weighted grading scheme:

# Elena's course grades
student_name = "Elena"
homework = 91.5       # 30% of final grade
midterm = 87.0        # 30% of final grade
final_exam = 93.0     # 40% of final grade

# Weighted average
weighted_average = (homework * 0.30) + (midterm * 0.30) + (final_exam * 0.40)

print(f"{student_name}'s weighted average: {weighted_average}")
Elena's weighted average: 90.75

🔄 Check Your Understanding

Without running the code, what does each expression evaluate to?

  1. 15 // 4
  2. 15 % 4
  3. 2 ** 4
  4. 10 / 5

Verify

  1. 3 — floor division drops the remainder
  2. 3 — the remainder of 15 divided by 4
  3. 16 — 2 raised to the 4th power
  4. 2.0 — true division always returns a float

3.5 String Expressions

Strings have their own operators. You can't divide a string by 2, but you can do other useful things.

3.5.1 Concatenation with +

The + operator joins strings together (concatenation):

first_name = "Elena"
last_name = "Vasquez"
full_name = first_name + " " + last_name
print(full_name)
Elena Vasquez

Notice we had to add a space " " between the names — + doesn't add spaces automatically.

3.5.2 Repetition with *

The * operator repeats a string:

line = "-" * 40
print(line)
print("REPORT HEADER")
print(line)
----------------------------------------
REPORT HEADER
----------------------------------------

This is handy for creating visual separators in text output.

3.5.3 String Length with len()

The len() function returns the number of characters in a string:

message = "Hello, world!"
print(len(message))

empty = ""
print(len(empty))
13
0

Spaces, punctuation, and special characters all count as characters.

# Practical use: validating input length
password = "abc123"
print(f"Password length: {len(password)}")
print(f"Long enough? {len(password) >= 8}")
Password length: 6
Long enough? False

⚠️ Pitfall: You can't use + to concatenate a string with a number. Python won't guess what you mean:

python age = 20 message = "I am " + age + " years old" # TypeError!

This produces a TypeError because Python doesn't know if you want to add numbers or join text. You must explicitly convert: "I am " + str(age) + " years old". Or better yet, use an f-string (section 3.8).


3.6 Boolean Expressions

Boolean expressions use comparison operators to produce True or False. These become essential in Chapter 4 for making decisions, but you need to understand them now as a type.

3.6.1 Comparison Operators

score = 87

print(score == 90)    # Equal to
print(score != 90)    # Not equal to
print(score > 80)     # Greater than
print(score < 80)     # Less than
print(score >= 87)    # Greater than or equal to
print(score <= 90)    # Less than or equal to
False
True
True
False
True
True
Operator Meaning Example Result
== Equal to 5 == 5 True
!= Not equal to 5 != 3 True
> Greater than 5 > 3 True
< Less than 5 < 3 False
>= Greater than or equal to 5 >= 5 True
<= Less than or equal to 5 <= 3 False

⚠️ Pitfall: = is assignment. == is comparison. Mixing them up is one of the most common beginner mistakes:

python x = 5 # Assignment: make x point to 5 x == 5 # Comparison: does x equal 5? (True)

If you write if x = 5: instead of if x == 5:, Python will give you a SyntaxError. This is actually Python being helpful — some other languages would silently do the wrong thing.

3.6.2 Comparing Strings

You can compare strings too. Python compares them character by character using Unicode (alphabetical) ordering:

print("apple" < "banana")    # True — 'a' comes before 'b'
print("apple" == "Apple")    # False — case matters!
print("abc" < "abd")         # True — differs at 3rd character: 'c' < 'd'
True
False
True

🔄 Check Your Understanding

What does each expression evaluate to?

  1. 10 == 10.0
  2. "hello" != "Hello"
  3. True == 1

Verify

  1. True — Python considers 10 and 10.0 equal (int and float comparison works).
  2. True — Python is case-sensitive; lowercase 'h' is not the same as uppercase 'H'.
  3. True — in Python, True is equivalent to 1 and False to 0. This is because bool is actually a subclass of int. Useful to know but rarely something you should rely on in your code.

3.7 Type Casting

Sometimes you need to convert a value from one type to another. This is called type casting (or type conversion). Python provides built-in functions for this.

3.7.1 The Casting Functions

# int() — converts to integer
print(int(3.7))         # 3 — truncates, does NOT round
print(int("42"))        # 42 — parses the string as a number
print(int(True))        # 1

# float() — converts to float
print(float(42))        # 42.0
print(float("3.14"))    # 3.14
print(float(False))     # 0.0

# str() — converts to string
print(str(42))          # "42"
print(str(3.14))        # "3.14"
print(str(True))        # "True"

# bool() — converts to boolean
print(bool(1))          # True
print(bool(0))          # False
print(bool("hello"))    # True
print(bool(""))         # False
3
42
1
42.0
3.14
0.0
42
3.14
True
True
False
True
False

3.7.2 When You Actually Need Type Casting

The most common case: input() always returns a string, even if the user types a number.

user_input = input("Enter your age: ")
print(type(user_input))           # <class 'str'> — always!

age = int(user_input)             # Convert to integer
print(type(age))                  # <class 'int'>
print(f"Next year you'll be {age + 1}")
Enter your age: 20
<class 'str'>
<class 'int'>
Next year you'll be 21

Without the int() conversion, trying to do user_input + 1 would crash with a TypeError.

3.7.3 Type Casting Gotchas

Not every conversion works. Python will tell you when it can't do what you're asking:

# These will crash
# int("hello")       # ValueError: invalid literal for int()
# int("3.14")        # ValueError: can't go str → int if string has a decimal
# float("abc")       # ValueError: could not convert string to float

🐛 Debugging Walkthrough: The TypeError

Elena is building a simple script and writes:

python name = "Elena" score = 95 message = "Student " + name + " scored " + score + " points." print(message)

She gets this error:

Traceback (most recent call last): File "report.py", line 3, in <module> message = "Student " + name + " scored " + score + " points." ~~~~~~^~~~~~~~~~~ TypeError: can only concatenate str (not "int") to str

What happened: Python tried to concatenate (join) the string " scored " with the integer 95. Python doesn't auto-convert; it raises a TypeError instead.

The fix: Convert score to a string, or (better) use an f-string:

```python

Fix 1: explicit conversion

message = "Student " + name + " scored " + str(score) + " points."

Fix 2: f-string (recommended — covered in section 3.8)

message = f"Student {name} scored {score} points." ```

The lesson: Python's TypeError is not a failure — it's the language telling you exactly what's wrong. The error message says "can only concatenate str (not int) to str." Read it carefully and you'll know the fix. This is a theme we'll return to throughout the book: errors are not failures, they are information.

3.7.4 The bool() Truthiness Rules

Python has a consistent rule for what's "truthy" (converts to True) and what's "falsy" (converts to False):

Value bool() Result Rule
0 False Zero is falsy
0.0 False Zero is falsy
"" (empty string) False Empty is falsy
None False None is falsy
Any non-zero number True Non-zero is truthy
Any non-empty string True Non-empty is truthy
print(bool(42))         # True
print(bool(-1))         # True
print(bool(0))          # False
print(bool("hi"))       # True
print(bool(""))         # False
print(bool(" "))        # True — a space is not empty!
True
True
False
True
False
True

This will matter a lot when we start writing if statements in Chapter 4.


3.8 f-Strings: Formatted Output

You've already seen f-strings a few times. Let's learn them properly. An f-string (formatted string literal) starts with f before the opening quote and lets you embed expressions directly inside curly braces {}.

3.8.1 Basic f-String Usage

name = "Elena"
score = 90.75

# Without f-strings (clunky)
print("Student: " + name + ", Score: " + str(score))

# With f-strings (clean)
print(f"Student: {name}, Score: {score}")
Student: Elena, Score: 90.75
Student: Elena, Score: 90.75

You can put any valid Python expression inside the curly braces:

a = 15
b = 4
print(f"{a} divided by {b} is {a / b}")
print(f"{a} modulo {b} is {a % b}")
print(f"The length of 'hello' is {len('hello')}")
15 divided by 4 is 3.75
15 modulo 4 is 3
The length of 'hello' is 5

3.8.2 Format Specifiers

You can control how values are displayed using format specifiers after a colon inside the braces:

average = 90.33333333333333

# Round to 2 decimal places
print(f"Average: {average:.2f}")

# Round to 1 decimal place
print(f"Average: {average:.1f}")

# Round to 0 decimal places (still shows as float)
print(f"Average: {average:.0f}")
Average: 90.33
Average: 90.3
Average: 90

More format specifiers you'll find useful:

price = 1234.5
percentage = 0.857

# Comma separator for thousands
print(f"Revenue: ${price:,.2f}")

# Percentage formatting
print(f"Accuracy: {percentage:.1%}")

# Padding and alignment
print(f"{'Name':<15}{'Score':>10}")      # left-align, right-align
print(f"{'Elena':<15}{90.75:>10.1f}")
print(f"{'Marcus':<15}{87.00:>10.1f}")
Revenue: $1,234.50
Accuracy: 85.7%
Name                Score
Elena                90.8
Marcus               87.0
Specifier Meaning Example Output
.2f 2 decimal places f"{3.14159:.2f}" 3.14
.1% Percentage, 1 decimal f"{0.857:.1%}" 85.7%
, Thousands separator f"{1000000:,}" 1,000,000
<15 Left-align in 15 chars f"{'hi':<15}" hi
>10 Right-align in 10 chars f"{42:>10}" 42

💡 Intuition: f-strings are one of Python's best features. They replaced three older formatting methods (%, .format(), and string concatenation), and they're faster and more readable than all of them. Get comfortable with f-strings now — you'll use them in virtually every program you write.

Now let's fix Elena's grade calculator with proper formatting:

student_name = "Elena"
homework = 91.5
midterm = 87.0
final_exam = 93.0
weighted_average = (homework * 0.30) + (midterm * 0.30) + (final_exam * 0.40)

print(f"{'='*40}")
print(f"  Grade Report: {student_name}")
print(f"{'='*40}")
print(f"  {'Homework:':<20}{homework:>8.1f}")
print(f"  {'Midterm:':<20}{midterm:>8.1f}")
print(f"  {'Final Exam:':<20}{final_exam:>8.1f}")
print(f"{'─'*40}")
print(f"  {'Weighted Average:':<20}{weighted_average:>8.2f}")
print(f"{'='*40}")
========================================
  Grade Report: Elena
========================================
  Homework:                91.5
  Midterm:                 87.0
  Final Exam:              93.0
────────────────────────────────────────
  Weighted Average:           90.75
========================================

3.9 Augmented Assignment

When you need to update a variable based on its current value, Python provides shorthand operators called augmented assignment operators:

score = 100

# Long form
score = score - 10
print(score)        # 90

# Augmented assignment (same thing, more concise)
score -= 10
print(score)        # 80
90
80

Here's the full set:

Long Form Augmented Example Result (if x = 10)
x = x + 5 x += 5 x += 5 15
x = x - 3 x -= 3 x -= 3 7
x = x * 2 x *= 2 x *= 2 20
x = x / 4 x /= 4 x /= 4 2.5
x = x // 3 x //= 3 x //= 3 3
x = x % 3 x %= 3 x %= 3 1
x = x ** 2 x **= 2 x **= 2 100

Augmented assignment also works with strings:

message = "Hello"
message += ", world!"
print(message)
Hello, world!

You'll use += constantly — it's the most common one by far. Running totals, building strings piece by piece, counting things.

# Running total — summing up scores
total = 0
total += 95
total += 87
total += 91
count = 3

print(f"Total: {total}")
print(f"Average: {total / count:.1f}")
Total: 273
Average: 91.0

3.10 How Python Manages Memory

Let's return to the name tag model from section 3.1 and prove it with actual code. Python provides the id() function, which returns the memory address of an object (think of it as the object's unique ID).

3.10.1 Two Names, One Object

a = 42
b = a

print(f"a = {a}, id(a) = {id(a)}")
print(f"b = {b}, id(b) = {id(b)}")
print(f"Same object? {id(a) == id(b)}")
a = 42, id(a) = 140735123456320
b = 42, id(b) = 140735123456320
Same object? True

(Your actual numbers will be different, but id(a) and id(b) will match.)

Here's what's happening in memory:

  Name Tags              Memory
  ┌───┐                ┌──────────┐
  │ a │ ──────────────▶│  42      │
  └───┘           ┌───▶│ (int)    │
  ┌───┐           │    └──────────┘
  │ b │ ──────────┘
  └───┘

Both a and b are name tags pointing to the same integer object 42 in memory. There's only one 42.

3.10.2 Rebinding a Name

Now watch what happens when we reassign a:

a = 42
b = a
print(f"Before: id(a) = {id(a)}, id(b) = {id(b)}")

a = 99    # Rebind a to a new object
print(f"After:  id(a) = {id(a)}, id(b) = {id(b)}")
print(f"a = {a}, b = {b}")
Before: id(a) = 140735123456320, id(b) = 140735123456320
After:  id(a) = 140735123458144, id(b) = 140735123456320
a = 99, b = 42

After the reassignment, a and b have different id() values. They point to different objects:

  Name Tags              Memory
  ┌───┐                ┌──────────┐
  │ a │ ──────────────▶│  99      │
  └───┘                │ (int)    │
                       └──────────┘
  ┌───┐                ┌──────────┐
  │ b │ ──────────────▶│  42      │
  └───┘                │ (int)    │
                       └──────────┘

The b name tag didn't move. Reassigning a only moved the a name tag. This is the name tag model in action.

🔬 Deep Dive: Python caches small integers (typically -5 to 256) for performance, so id(42) will always return the same address. But this is an implementation detail — don't write code that depends on it. The name tag model is the correct way to think about it regardless.

3.10.3 Why This Matters

With integers, floats, strings, and booleans, the name tag model and the box model happen to predict the same behavior. But in Chapter 8, when you work with lists (which are mutable — they can be changed in place), the distinction becomes critical:

# Preview of why the name tag model matters (Chapter 8)
list_a = [1, 2, 3]
list_b = list_a       # Both names point to the SAME list
list_a.append(4)      # Modify the list through name tag a
print(list_b)         # [1, 2, 3, 4] — surprise! b sees the change too

If you were using the box model, you'd expect list_b to still be [1, 2, 3]. But both names point to the same object, so changes through one name are visible through the other. Getting the name tag model right now saves you from this confusion later.

🔗 Connection to Computational Thinking: The name tag model is an example of abstraction — one of the four pillars from Chapter 1. Python abstracts away the details of memory management (you never manually allocate or free memory), but understanding the concept of names pointing to objects helps you predict how your programs will behave. This is exactly the kind of "thinking behind the code" that separates beginners from confident programmers.


3.11 Project Checkpoint: TaskFlow v0.2

📐 Project Checkpoint: TaskFlow v0.2 — Add a Single Task with Timestamp

In Chapter 2, TaskFlow v0.1 greeted the user. Now we'll add a real feature: the user can enter a task name, and the program stores it with a timestamp showing when it was created.

Here's the full code for TaskFlow v0.2:

# taskflow_v02.py — TaskFlow v0.2: Add a task with timestamp
from datetime import datetime

# Greet the user (carried over from v0.1)
print("=" * 40)
print("  Welcome to TaskFlow v0.2")
print("=" * 40)

user_name = input("What's your name? ")
print(f"\nHello, {user_name}! Let's add a task.\n")

# Get task details
task_name = input("Enter a task: ")
task_created = datetime.now()

# Format the timestamp
timestamp_str = task_created.strftime("%Y-%m-%d %H:%M:%S")

# Display the task
print(f"\n{'─' * 40}")
print(f"  Task added successfully!")
print(f"{'─' * 40}")
print(f"  Task:    {task_name}")
print(f"  Added:   {timestamp_str}")
print(f"  Status:  Pending")
print(f"{'─' * 40}")

# Show variable types for learning purposes
print(f"\n📊 Behind the scenes:")
print(f"  task_name is a {type(task_name).__name__}: '{task_name}'")
print(f"  task_created is a {type(task_created).__name__}: {task_created}")
print(f"  timestamp_str is a {type(timestamp_str).__name__}: '{timestamp_str}'")
========================================
  Welcome to TaskFlow v0.2
========================================
What's your name? Elena

Hello, Elena! Let's add a task.

Enter a task: Finish Ch3 exercises

────────────────────────────────────────
  Task added successfully!
────────────────────────────────────────
  Task:    Finish Ch3 exercises
  Added:   2025-09-15 14:32:07
  Status:  Pending
────────────────────────────────────────

📊 Behind the scenes:
  task_name is a str: 'Finish Ch3 exercises'
  task_created is a datetime: 2025-09-15 14:32:07.123456
  timestamp_str is a str: '2025-09-15 14:32:07'

What's new in v0.2:

  • Variables to store data (user_name, task_name, task_created)
  • Type awareness — datetime.now() returns a datetime object, which we convert to a formatted string
  • f-strings for clean output formatting
  • String repetition ("=" * 40) for visual structure

What's coming in v0.3 (Chapter 4): We'll add task priority (high/medium/low) using conditionals, and validate user input so the program handles unexpected entries gracefully.

🔄 Check Your Understanding

  1. What type is task_name? What type is task_created?
  2. Why do we call task_created.strftime(...) instead of just printing task_created directly?
  3. What would happen if we wrote print("Task: " + task_name + ", Added: " + task_created) without converting task_created to a string?

Verify

  1. task_name is a str (from input()). task_created is a datetime object (from datetime.now()).
  2. strftime() lets us control the format — we get "2025-09-15 14:32:07" instead of "2025-09-15 14:32:07.123456" (with microseconds). It's also type casting in disguise: converting a datetime to a string in a specific format.
  3. You'd get a TypeError — you can't concatenate a string with a datetime object. You'd need str(task_created) or an f-string.

Spaced Review

🔄 Retrieval Practice — Reaching Back

From Chapter 1 (Computational Thinking): Without looking back, list the four pillars of computational thinking and give a one-sentence definition of each.

Verify

  1. Decomposition: Breaking a complex problem into smaller, manageable pieces.
  2. Pattern Recognition: Spotting similarities between new problems and ones you've solved before.
  3. Abstraction: Ignoring irrelevant details to focus on what matters.
  4. Algorithm Design: Creating a precise, step-by-step procedure to solve a problem.

Bridge Question (connecting Ch 1 to Ch 3): In this chapter, you learned that variables are an implementation of abstraction — they let you give a meaningful name to a value and use it without caring about where it lives in memory. Identify one other example of abstraction from this chapter. (Hint: think about f-strings.)

Verify

f-strings are an abstraction over string formatting. You write f"Score: {score:.2f}" and Python handles the conversion, rounding, and insertion for you. You don't need to know how Python internally converts a float to a two-decimal-place string representation — the f-string hides that complexity behind a simple {expression:format} syntax.


3.12 Chapter Summary

Key Concepts

  • Variables are name tags (labels) that point to objects in memory — not boxes that hold values.
  • Assignment (=) binds a name to an object. Reassignment moves the name tag; it doesn't change the old object.
  • Python has four fundamental types: int, float, str, and bool.
  • Expressions combine values, variables, and operators to produce results.
  • Type casting (int(), float(), str(), bool()) converts between types explicitly.
  • f-strings are the modern, readable way to format output with embedded expressions.
  • Augmented assignment (+=, -=, etc.) provides concise syntax for updating variables.
  • type() reveals what type a value is; id() reveals where it lives in memory.

Key Terms Summary

Term Definition
Variable A name that refers to an object stored in memory
Assignment Binding a name to a value using the = operator
Integer (int) A whole number: positive, negative, or zero
Float (float) A number with a decimal point
String (str) A sequence of characters (text)
Boolean (bool) A logical value: True or False
Expression A combination of values, variables, and operators that evaluates to a result
Operator A symbol that performs an operation (e.g., +, -, *, ==)
Type casting Converting a value from one type to another (e.g., int("42"))
f-string A formatted string literal (f"...") that embeds expressions inside {}
type() A built-in function that returns the type of a value
Augmented assignment Shorthand operators like += and -= that update a variable in place

What You Should Be Able to Do

  • [ ] Create variables with meaningful names following PEP 8 conventions
  • [ ] Identify and use all four fundamental types: int, float, str, bool
  • [ ] Write arithmetic expressions and predict the result using operator precedence
  • [ ] Convert between types using int(), float(), str(), bool()
  • [ ] Format output with f-strings, including format specifiers for decimal places
  • [ ] Explain the name tag model: two variables can point to the same object
  • [ ] Use type() and id() to inspect values at runtime

What's Next

In Chapter 4: Making Decisions: Conditionals and Boolean Logic, you'll learn how to make your programs choose between different paths based on conditions. The boolean expressions you learned in section 3.6 become the building blocks for if, elif, and else statements — the decision-making machinery of every program. You'll also meet Python's match/case statement and build TaskFlow v0.3, which adds task priorities and input validation.

The combination of variables (this chapter) and conditionals (next chapter) is where programs start to feel intelligent. They stop doing the same thing every time and start responding to data.

Before moving on, complete the exercises and quiz to solidify your understanding.


Chapter 3 Exercises → exercises.md

Chapter 3 Quiz → quiz.md

Case Study: Building a Unit Converter → case-study-01.md

Case Study: How a Simple Typo Cost a Company Millions → case-study-02.md