New: Try Voli The Bear, Fast package manager (and not only) for Windows
Updated Jun 19, 2026 Edit on GitHub

Syntax, Values & Types

Most languages use curly braces {} and semicolons to mark code blocks. Python uses something you can't see: whitespace. Getting it wrong produces mysterious errors until you know the rule, so it's worth meeting head-on - then we'll cover the handful of value types Python is built from.

The big surprise: indentation is structure

What it actually is. In Python, a line's indentation - how many spaces it's pushed in from the left - groups lines into a block. Where other languages write { ... }, Python says "these lines are indented under that line, so they belong to it." Indentation isn't decoration; it's the grammar.

A block of code that runs only when a condition is true (if proper is Phase 4 - focus on the shape for now):

temperature = 30
if temperature > 25:
    print("It's warm")
    print("Wear a t-shirt")
print("Done checking")

What just happened: The line ending in : opens a block. The two indented lines run only when temperature > 25; the last print, back at the left margin, is outside the block and always runs. Since 30 > 25, all three lines print:

It's warm
Wear a t-shirt
Done checking

📝 Terminology. The : and the indented lines under it form a block (also called a suite). Standard is 4 spaces per level - don't mix tabs and spaces, and let your editor insert four on Tab.

⚠️ IndentationError and TabError. Because spacing is the structure, getting it wrong is a real error, not a style nitpick. Indent a line that shouldn't be, or mix tabs with spaces, and Python refuses to run:

  File "warm.py", line 3
    print("It's warm")
    ^
IndentationError: unexpected indent

This confuses everyone at first. Fix: make every line in the block start at the same column, spaces only - set your editor to "insert spaces for tabs" and it'll handle this for you.

💡 Key point. You don't indent because it looks nice - the indentation is how you tell Python which lines belong together. Read it the way you'd read braces in other languages.

Variables - names pointing at values

What it actually is. A variable is a name attached to a value so you can refer to it later. Create one with = - read it as "let this name refer to this value," not as math equality.

name = "Ada"
age = 36
print(name)
print(age)

What just happened: name = "Ada" made name refer to the text "Ada"; age = 36 made age refer to 36. Each print looked up what the name points at and showed it:

Ada
36

Re-point a name at a new value any time - that's the whole idea of a variable:

score = 10
score = score + 5
print(score)

What just happened: score + 5 was computed first using the current value of score (10), giving 15; then score = re-pointed the name at that value, so it prints 15. The name didn't "change" - you pointed it somewhere new.

The core types

Every value in Python has a type - what kind of thing it is. You'll use five constantly. Ask any value its type with the built-in type():

print(type(36))
print(type(3.14))
print(type("hello"))
print(type(True))
print(type(None))

What just happened: type() reports each value's type:

<class 'int'>
<class 'float'>
<class 'str'>
<class 'bool'>
<class 'NoneType'>

What each is for:

  • int - a whole number, no decimal point: 36, 0, -7. No size limit; grows as large as memory allows.
  • float - a number with a decimal point: 3.14, 2.0, -0.5. Short for floating-point, how computers store fractional numbers.
  • str - a string: text in quotes. "hello", 'Ada'. Single or double both work; pick one and be consistent.
  • bool - a boolean: True or False (capitalized - Python is picky). The type of any yes/no answer, like a comparison's result.
  • None - a special value meaning "nothing here / no value yet." Its type is NoneType; there's only ever one None. You'll meet it as the default result of functions that don't return anything.

📝 Terminology. A string is a sequence of characters - text. The quotes aren't part of the value; they just tell Python "the text starts here and ends there."

Dynamic typing - names don't have a fixed type

What it actually is. Some languages require declaring "this variable holds an integer," and it can only ever hold integers. Python doesn't work that way: a name can point at one type now and a different type later - the value has a type, the name is just a label.

x = 42
print(type(x))
x = "now I'm text"
print(type(x))

What just happened: x first pointed at an int, then got re-pointed at a str. Python allows this freely, so it prints two different types:

<class 'int'>
<class 'str'>

This is called dynamic typing: flexible and quick to write, but nothing stops you from putting the wrong kind of value in a name - you only find out when something breaks later. Keep each variable holding one kind of thing to avoid most of that pain.

f-strings - putting values into text

You'll constantly build strings out of fixed text plus values. The clean, modern way is an f-string: a string prefixed with f, where anything inside { } is replaced by that expression's value.

name = "Ada"
age = 36
print(f"{name} is {age} years old")

What just happened: The f before the quote marks an f-string. Python evaluated {name} and {age} and dropped their values straight into the text:

Ada is 36 years old

Any expression can go inside the braces, not only a bare name:

price = 4
print(f"Two coffees cost {price * 2} dollars")

What just happened: Python computed price * 2 (8) and slotted the result in:

Two coffees cost 8 dollars

💡 Key point. Reach for f-strings whenever you mix text and values - readable, fast, modern. (Older code uses "%s" % x or .format(); you'll see those around but don't need to write them.)

Two number traps that bite beginners

⚠️ = vs == - assignment vs comparison. A single = assigns (points a name at a value); a double == compares two values and returns a boolean. Mixing them up is a common early mistake:

x = 5
print(x == 5)
print(x == 6)

What just happened: x = 5 assigned. x == 5 asked "is x equal to 5?" - True - and x == 6 asked the same about 6 - False:

True
False

Say it in your head as you type: = is "set to," == is "is equal to?"

⚠️ Integer vs float division - / always gives a float. / always produces a float, even when the numbers divide evenly. For whole-number division, use //:

print(7 / 2)
print(8 / 2)
print(7 // 2)
print(7 % 2)

What just happened: / gave floats (note 4.0, not 4). // did floor division - divide, discard the remainder, giving a whole number. % (modulo) gave the remainder:

3.5
4.0
3
1

This surprises people coming from languages where / on two integers stays an integer - in Python, / always means a decimal. Use // for the whole part, % for what's left over (handy for "is this number even?" - n % 2 == 0).

Recap

  1. Indentation is structure. A : opens a block; indented lines under it belong to it. Use 4 spaces, never tabs - getting it wrong is a real IndentationError.
  2. A variable is a name pointing at a value; = means "let this name refer to," re-pointable any time.
  3. Five everyday types: int (whole), float (decimal), str (text), bool (True/False), None (no value). type(x) tells you which.
  4. Python is dynamically typed - a name can hold and change type. Flexible, but keep each name to one kind of thing.
  5. f-strings (f"{name} is {age}") slot values into text; reach for them by default.
  6. = assigns, == compares. / always yields a float; use // for whole-number division, % for the remainder.

Next: the lists, tuples, dicts, and sets you'll use to hold collections of things.


← Phase 1: Install & Your First Program · Guide overview · Phase 3: Collections →

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 3 questions

1. In Python, what marks where a block of code begins and ends?

2. What does `7 / 2` produce in Python, and how do you get whole-number division?

3. What does dynamic typing mean in Python?