🐍 Python Course · Stage 1 · Lesson 3 of 89 · Variables & Types
Course Overview →

🟩 Start with the Beginner tab. Read it fully before moving on.

← Setting Up Python f-strings →
thecodex.expert · The Codex Family of Knowledge
Python

Variables and Types in Python

Python has no type declarations — assign a value and Python figures out the rest. But that doesn't mean it's careless with types.

Python 3.13 docs.python.org Last verified:
Canonical Definition

In Python, a variable is a name bound to an object — every value is an instance of some class, types are determined at runtime (dynamic typing), and the language enforces type compatibility without silent coercion (strong typing).

🟩 Beginner

Start here — no prior experience needed

What you'll learn in this tab: How to create variables, what Python's 12 built-in types are, and how to choose between them. By the end you'll be able to write the first few lines of any Python program — storing names, numbers, and collections of data.

How to read this tab: Read each section once, then look at the code example and predict what each line does before reading the comments. If you don't understand a line, that's fine — move on and come back. This is your first pass, not a test.

⏱ Approx. 35 minutes 📄 2 sections · 2 code examples 🟩 No prerequisites
The analogy

A variable is a label on a box. Python doesn't care what's in the box — you can swap the contents for anything. The label just tells you where to find it.

💡

What this section is: The core mechanics of creating and naming variables in Python. Focus on the naming conventions at the bottom — Python's style is lowercase with underscores (user_score, not userScore). This matters from day one because everyone who reads your code expects it.

Variables

In Python, you create a variable by writing a name, an equals sign, and a value. No type declaration needed — Python figures out the type from the value you assign. You can reassign a variable to a completely different type at any time.

Pythonvariables_basics.py
# Create a variable — no declaration keyword
name = "Priya"          # str
age = 28                # int
height = 5.4            # float
is_active = True        # bool
nothing = None          # NoneType

# Reassign — Python allows changing type
age = "twenty-eight"    # now a str — perfectly valid

# Multiple assignment
x, y, z = 1, 2, 3      # tuple unpacking
a = b = c = 0           # same value, three names

# Check the type
print(type(name))       # <class 'str'>
print(type(age))        # <class 'int'>  (before reassignment)

# Variable names: lowercase, underscores, descriptive
user_score = 95         # good
userScore = 95          # camelCase — works but not Python style
GRAVITY = 9.8           # UPPERCASE = conventional constant

This is the most important table on this page. Don't memorise all 12 types right now — focus on the Mutable? column. The types marked Yes (list, dict, set) behave differently when you pass them to functions. You'll feel why this matters in the next few pages. For now, just notice which are mutable and which aren't.

The built-in types

TypeExampleMutable?Notes
int42, -7, 0NoArbitrary precision — no overflow
float3.14, 1e10NoIEEE 754 double precision
complex2+3jNoBuilt-in complex numbers
boolTrue, FalseNoSubclass of int: True==1, False==0
str"hello", 'world'NoUnicode text, immutable
bytesb"data"NoRaw binary data
list[1, 2, 3]YesOrdered, indexed, heterogeneous
tuple(1, 2, 3)NoImmutable list — use for fixed data
dict{"a": 1}YesKey-value, insertion-ordered (3.7+)
set{1, 2, 3}YesUnordered, unique values
frozensetfrozenset({1,2})NoImmutable set
NoneTypeNoneNoThe absence of a value
Pythontypes_overview.py
# Numbers
x = 42              # int
y = 3.14            # float
z = 2 + 3j          # complex

# Python ints have no overflow
big = 10 ** 100     # a googol — works fine

# Strings — single or double quotes, same thing
s1 = "hello"
s2 = 'hello'
s3 = """Multi
line string"""

# f-strings (Python 3.6+) — the preferred way
name = "Priya"
msg = f"Hello, {name}! Age: {age}"

# Collections
fruits = ["mango", "apple", "banana"]  # list — mutable
point  = (10, 20)                       # tuple — immutable
scores = {"Priya": 95, "Rahul": 88}    # dict
unique = {1, 2, 3, 2, 1}               # set — {1, 2, 3}

# None — Python's null equivalent
result = None
print(result is None)   # True — use `is`, not ==
Try it: Open the Python Playground and type print(type(42)), then print(type("hello")), then print(type([1,2,3])). The output shows you the type of each value in real time.

✅ Beginner tab complete — check your understanding

  • I can create a variable with name = value and reassign it to a different type
  • I know the difference between a list (mutable) and a tuple (immutable)
  • I know the difference between a dict (key-value pairs) and a set (unique values)
  • I know that None represents "no value" and that I check it with is None
  • I know Python variable names use lowercase_underscores, not camelCase

Ticked all five? Click the 🔵 Intermediate tab to go deeper — or come back to it after you've done the next two lessons first. The Intermediate tab explains why Python's type system works the way it does, and introduces the concept that trips up almost every beginner: mutability.

🔵 Intermediate

The concepts that make Python click

What you'll learn in this tab: Why Python's type system is both flexible and strict. How mutability actually works in memory — and why it produces the bugs that trip up every beginner. The difference between a variable as a "name" and a variable as a "box". And how to add type hints to your code so editors can catch mistakes before you run it.

Recommended for: Read this tab after you've done 2–3 Beginner lessons and written some code. The mutability section especially makes much more sense once you've passed a list to a function and been surprised by the result.

⏱ Approx. 40 minutes 📄 4 sections · 3 code examples 🔶 After Beginner tab + Lesson 4 (Control Flow)
💡

What this section is: The terminology behind Python's type system. Two terms matter here: dynamically typed (types are checked when code runs, not before) and strongly typed (Python won't secretly convert one type to another). You'll use these terms when talking to other developers and when reading Python documentation.

How Python's type system works

Python is dynamically typed — types are checked at runtime, not compile time. Python is also strongly typed — it will not silently coerce incompatible types. "5" + 3 raises a TypeError in Python (unlike JavaScript where it gives "53"). Every object in Python carries its type at runtime — you can always check with type() or isinstance().

📌

What this section is: An introduction to type hints — optional annotations that tell tools (and humans) what type a variable should be. Important: Python does not enforce these at runtime. They are purely for editors, static analysis tools like mypy, and documentation. You don't need to use them immediately, but you'll see them everywhere in professional code.

Type hints (PEP 484, Python 3.5+)

Python 3.5 added optional type annotations. They are not enforced at runtime but are used by static analysis tools (mypy, pyright, Pyrefly) and IDEs. The Python interpreter completely ignores them at runtime — x: int = "hello" runs without error.

Pythontype_hints.py
from typing import Optional, Union

# Function with type hints
def greet(name: str, times: int = 1) -> str:
    return (f"Hello, {name}! " * times).strip()

# Variable annotations
score: int = 95
label: str

# Optional — the value may be None
def find_user(id: int) -> Optional[str]:
    if id == 1: return "Priya"
    return None

# Union — one of several types (Python 3.10+ shorthand: str | int)
def process(value: Union[str, int]) -> str:
    return str(value)

# Modern syntax (3.10+)
def divide(a: float, b: float) -> float | None:
    return a / b if b != 0 else None

# Type annotations don't change runtime behaviour
x: int = "hello"    # No error at runtime!
print(type(x))      # <class 'str'>

This is the most important section on this page. Mutability is the concept that causes the most unexpected bugs for Python beginners. Read this section slowly. Run the code examples — especially the mutable default argument gotcha at the bottom. If you only understand one thing from this entire page, make it this: when you write b = a where a is a list, both names point to the same list.

Mutability in depth

Mutability is one of Python's most important concepts. Immutable objects (int, str, tuple, frozenset) cannot be changed after creation — what looks like "changing a string" always creates a new string. Mutable objects (list, dict, set) can be modified in-place. This matters enormously when passing objects to functions and when using objects as dictionary keys (only immutable objects are hashable and can be keys).

Pythonmutability.py
# Immutable — "changing" creates a new object
s = "hello"
id_before = id(s)
s = s + " world"    # new string object
print(id(s) == id_before)   # False — different object

# Mutable — modified in place
lst = [1, 2, 3]
id_before = id(lst)
lst.append(4)
print(id(lst) == id_before)  # True — same object

# Tuple vs list — same data, different mutability
point_list  = [10, 20]   # mutable
point_tuple = (10, 20)   # immutable

# Only immutable objects can be dict keys / set members
d = {(1, 2): "a point"}        # tuple key — OK
# d = {[1, 2]: "a point"}      # TypeError: list is unhashable

# Gotcha: mutable default argument
def add_item(item, lst=[]):     # DON'T do this
    lst.append(item)
    return lst
print(add_item(1))  # [1]
print(add_item(2))  # [1, 2] — shared list!

def add_item_safe(item, lst=None):  # correct pattern
    if lst is None: lst = []
    lst.append(item)
    return lst
💡

What this section is: A more precise model of what a variable actually is in Python — a name bound to an object, not a box containing a value. This distinction explains why a = b on a list means they share the same object. The is vs == distinction at the end is essential: always use == to compare values, and is only when checking for None.

Names, objects, and identity

In Python, variables are names bound to objects — not boxes containing values. When you write a = b, you bind the name a to the same object that b points to. For mutable objects, this means both a and b can see mutations. is checks identity (same object), == checks equality (same value).

Pythonnames_objects.py
a = [1, 2, 3]
b = a           # b and a point to the SAME list
b.append(4)
print(a)        # [1, 2, 3, 4] — a sees the change!

c = a.copy()    # c is a new list with the same values
c.append(5)
print(a)        # [1, 2, 3, 4] — a unchanged

# Identity vs equality
x = [1, 2, 3]
y = [1, 2, 3]
print(x == y)   # True  — same values
print(x is y)   # False — different objects

# Small int caching: CPython caches -5 to 256
a = 256; b = 256
print(a is b)   # True — same cached object
a = 257; b = 257
print(a is b)   # False — not cached (implementation detail)
Commonly confused
Dynamic typing is not the same as weak typing. Python is dynamically typed (types checked at runtime) AND strongly typed (no silent coercion between incompatible types). JavaScript is weakly typed — it will coerce "5" + 3 to "53". Python raises TypeError.
is is not ==. is checks object identity (same memory address). == checks equality (same value). Always use == for value comparison. Use is only for None, True, False, and explicit identity checks.
Type hints are not enforced at runtime. def f(x: int) -> str: return x will not raise an error at runtime even if you pass a string and return an int. Type hints are documentation + static analysis only.
How this connects
Requires first

✅ Intermediate tab complete — check your understanding

  • I can explain the difference between dynamically typed and strongly typed
  • I understand why b = a on a list means both names see the same mutations
  • I know never to use a mutable object (like [] or {}) as a default argument
  • I know to use == for value comparison and is only for None
  • I understand what a type hint like def greet(name: str) -> str means, even if I'm not using them yet

Want to go deeper? The 🔴 Expert tab explains how Python's object model works at the CPython level — why id() returns a memory address, how reference counting garbage collection works, and the full type hierarchy. Save it for when you're curious about why, not just what.

Ready to move on? Go to Control Flow & Loops → — that's the next lesson in the course.

🔴 Expert

Under the hood — CPython's object model

What you'll learn in this tab: How Python's object model works at the CPython implementation level — why id() returns a memory address, how reference counting decides when memory is freed, the complete type class hierarchy, and the implementation details behind integer and string interning.

When to read this tab: When you're genuinely curious about why Python behaves the way it does — not just how to use it. This content will not help you write better programs immediately, but it will help you understand error messages, debug unexpected behaviour, and speak credibly with experienced engineers. Read it after you've finished Stage 2 of the course.

⏱ Approx. 20 minutes 📄 3 sections 🔴 After completing Stage 2
📌

What this section is: The formal definition of what an "object" is in Python's data model. Every value has three things: an identity (id()), a type (type()), and a value. Understanding this makes is, reference counting, and garbage collection make intuitive sense. The key insight: id() in CPython returns the memory address — which is why is is an identity check, not an equality check.

Python's data model: everything is an object

In Python, every value is an instance of some class — integers, strings, functions, classes themselves, and even None. The Python data model (Python Language Reference §3) defines the abstract base for all objects: every object has an identity (id() returns the memory address in CPython), a type (type()), and a value. The id() of an object is unique and constant for its lifetime. CPython's implementation uses reference counting: each object stores how many names point to it. When the count reaches zero, the object is freed.

📌

What this section is: The complete Python type hierarchy — who inherits from whom. The key fact: bool is a subclass of int (which is why True + True == 2 in Python). And type is its own metaclass — type(type) is type is True. You don't need to memorise the hierarchy, but knowing it exists helps when you encounter isinstance() checks in production code.

The type hierarchy

Python's type hierarchy (Python Language Reference §3.2): None · NotImplemented · Ellipsis → numbers (int → bool, complex, float) → sequences (immutable: str, bytes, tuple; mutable: list, bytearray) → set types (frozenset, set) → mappings (dict) → callables (functions, methods, classes) → modules → types. All types are instances of type; type itself is an instance of type (the metaclass of everything).

What this section is — and a warning. CPython (the standard Python implementation) caches small integers and certain strings as an optimisation. This is an implementation detail, not part of the Python language specification. Never write code that relies on this. Code that uses is to compare integers or strings may work in CPython but fail in PyPy, Jython, or other implementations. The only safe uses of is are: is None, is True, is False.

Integer interning and string interning

CPython interns (caches) small integers from -5 to 256 — all accesses to these integers reuse the same object. CPython also interns string literals that look like identifiers (alphanumeric, starting with letter or underscore) at compile time. This is an implementation detail of CPython, not guaranteed by the Python language specification. Code that relies on is for integers or strings is implementation-specific and may break in PyPy or other implementations.

✅ Expert tab complete

  • I can explain why id(x) returns a memory address in CPython
  • I know that bool is a subclass of int, which is why True == 1
  • I understand reference counting — when no names point to an object, it is freed
  • I know never to rely on integer or string interning in production code

You've completed all three levels of this page. Continue the course: Control Flow & Loops →

Sources

1
Python Language Reference §3 — Data model. docs.python.org/3/reference/datamodel.html.
2
Python Language Reference §4 — Execution model. docs.python.org/3/reference/executionmodel.html.
3
PEP 484 — Type Hints. peps.python.org/pep-0484/.
4
Python Built-in Types documentation. docs.python.org/3/library/stdtypes.html.
Source confidence: High Last verified: Primary source: Python Language Reference · docs.python.org/3/reference/