Skip to content

Variables & The Reference Model

In many introductory programming courses, a variable is taught as a “box” where you store data. While this analogy works for languages like C or Pascal, it is fundamentally incorrect for Python.

In Python, variables are not boxes; they are labels (or nametags) that refer to objects. Understanding this “Reference Model” is the single most important step in moving from a beginner to an intermediate Python developer.


At its core, a variable in Python is a name that is bound to an object. This binding occurs when you use the assignment operator (=).

A Variable is a symbolic name that serves as a reference or pointer to an object. When an object is assigned to a variable, you are “binding” that name to the object in memory.

Why does this distinction matter? In “Box Model” languages, if you assign x = 10 and then y = x, you have two separate boxes, each containing the number 10. In Python, you have one object (the number 10) with two labels (x and y) pointing to it. This leads to significantly different behavior when dealing with complex data structures.


To understand variables, we must first understand what they point to. Every “thing” in Python is an object. Every object has three non-negotiable properties:

  1. Identity: A unique identifier (essentially its address in memory). Once an object is created, its identity never changes.
  2. Type: What kind of data the object represents (e.g., int, str, list). This determines what operations can be performed on it.
  3. Value: The actual data held by the object (e.g., the number 42 or the string "Hello").
object_anatomy.py
x = [1, 2, 3]
print(f"Identity: {id(x)}") # Memory address
print(f"Type: {type(x)}") # <class 'list'>
print(f"Value: {x}") # [1, 2, 3]

3. Detailed Explanation: The Assignment Process

Section titled “3. Detailed Explanation: The Assignment Process”

When you execute a line of code like a = 1000, Python performs a very specific sequence of events “under the hood.”

  1. Object Creation: Python looks at the right-hand side of the = operator. It sees the literal 1000. It creates an integer object in memory to represent the value 1000.
  2. Memory Allocation: This object is assigned a memory address (Identity) and its type is set to int.
  3. Binding: Python looks at the left-hand side. It sees the name a. If a doesn’t exist, it creates it in the current “Namespace.” It then “points” the name a to the memory address of the object created in Step 1.

If you then write b = a, Python does not create a new object. Instead, it simply binds the name b to the exact same memory address that a is already pointing to.

shared_reference.py
a = [1, 2, 3] # Create a list object
b = a # Point 'b' to the SAME list object
print(id(a) == id(b)) # Output: True

What happens when we “change” a variable?

Scenario A: Immutable Objects (Integers, Strings)

Section titled “Scenario A: Immutable Objects (Integers, Strings)”

If you have x = 5 and then x = 6, you haven’t changed the number 5 into a 6. Instead:

  1. A new object <6> is created.
  2. The label x is detached from <5> and attached to <6>.
  3. The object <5> is left behind (and eventually cleaned up).

Scenario B: Mutable Objects (Lists, Dictionaries)

Section titled “Scenario B: Mutable Objects (Lists, Dictionaries)”

This is where the Reference Model becomes dangerous. If a and b point to the same list, and you modify that list through a, the change is visible through b.

mutability_trap.py
a = [1, 2, 3]
b = a
a.append(4)
print(f"a: {a}")
print(f"b: {b}")

Output:

a: [1, 2, 3, 4]
b: [1, 2, 3, 4]

Wait, why? Because a and b are just two names for the same physical object in memory. If you paint the “house” red using the name a, it’s still red when you look at it using the name b.


In the CPython source code (the standard version of Python), every object is defined by a C struct called PyObject.

Every PyObject contains at least two things:

  1. ob_refcnt (Reference Count): An integer that tracks how many variables are currently pointing to this object. When this hits zero, Python’s Garbage Collector deletes the object.
  2. ob_type (Type Pointer): A pointer to another object that defines the type of this object (e.g., pointing to the “Integer” type definition).

To save memory and increase speed, Python performs Interning on small integers (typically -5 to 256) and certain strings.

interning_demo.py
x = 10
y = 10
print(x is y) # True (They point to the SAME pre-allocated object)
a = 1000
b = 1000
print(a is b) # False (Two different objects with the same value)

Context: Python pre-creates these small integers when the interpreter starts because they are used so frequently. This is an implementation detail that shows how deeply Python manages its reference model for performance.


Since variables are labels, their names should be descriptive and follow the community-standard PEP 8 guidelines.

  • Case: Use snake_case (all lowercase, underscores for spaces).
  • Clarity: Use user_age instead of ua.
  • Booleans: Prefix with is_ or has_ (e.g., is_authenticated).
ConceptBox Model (C/C++)Reference Model (Python)
VariableA memory location containing a value.A label pointing to a memory location.
Assignment (x = y)Copies the value from y into x.Points x to the same object as y.
ReassignmentOverwrites the value in the box.Points the label to a new object.
MemoryManually managed (often).Automatically managed via Ref Counting.