Object identity: id() and memory references

RoadmapsPython

Overview

Every Python object has a unique identity — its location in memory — which you read with `id()`. The `is` operator compares identity (are these the exact same object?), while `==` compares value (do they have equal contents?). Two separate lists with the same items are `==` but not `is`, because they are distinct objects in memory. Only a name assigned from another (like `c = a`) shares identity. Think of `id()` as a house's street address and `==` as two houses having identical furniture: the same address is literally the same house, but identical furniture is not. # Identity sharing trap a = [1, 2] b = a b.append(3) print(a) # [1, 2, 3] - 'a' was modified! When multiple variables point to the exact same mutable object in memory, modifying one variable modifies the underlying object for all variables sharing that identity. This is covered fully in `shallow-vs-deep-copy`.

Confusing `is` with `==` causes real bugs in production code. You should reserve `is` exclusively for checking singletons like `None`, `True`, and `False`. Checking values with `is` (like `x is 1000`) can pass or fail unpredictably because Python only caches some objects in memory. Always use `==` when comparing values to ensure correct behavior.

Where used: None checks in API handlers, Pydantic validators, debugging shared references

Why learn this

Code walkthrough

a = {'id': 1}
b = {'id': 1}
c = a
print(a == b)
print(a is b)
print(a is c)

Focus: The dictionaries `a` and `b` are equal (`==`) but not identical (`is`). Only `c`, assigned from `a`, is the same object in memory.

Aha moment

a = 256
b = 256
print(a is b)

big = 1000
print(big is int('1000'))

Prediction: Both lines compare equal integers with `is`. What does each print?

Common guess: True for both

CPython caches small integers (from `-5` to `256`), so `256` is always that one cached object (so `is` evaluates to `True`). The integer `1000` is outside the cache, and `int('1000')` builds a fresh object at runtime, so it is a different object (so `is` evaluates to `False`). Use `==` for values, and reserve `is` for `None`, `True`, and `False`.

Common mistakes

Glossary

cached
Storing frequently used data in a temporary, fast-access memory area so it doesn't have to be recreated every time. For example, CPython caches small integers so `a = 5; b = 5; a is b` evaluates to `True`.
CPython
The standard, original, and most widely used version of the Python programming language, written in C. For example, running `python script.py`.

Recall questions

Understanding checks

What will be printed when this script is run?

True, False, True

The check `a == b` evaluates to `True` because their contents match. The check `a is b` evaluates to `False` because `b` is a freshly created list with a different memory address. The check `a is c` evaluates to `True` because `c` is assigned directly from `a`, so they reference the exact same object in memory.

A developer is writing a validator that ensures a user's balance matches exactly `1000`. Identify the bug.

The function uses the `is` operator to check for equality of the value `1000`.

Because CPython only caches small integers, using `is` to compare values like `1000` can fail unpredictably depending on how the object was created in memory. To compare values, you must use `==`. The `is` operator should only be used for `None`, `True`, and `False`.

Practice tasks

Fixing the Identity Check

The function below incorrectly uses `is` to check if a user's input matches a specific integer, and uses `==` to check if a result is `None`. Modify the code to use the correct operators for value comparison and identity checking.

Challenge

A correct None check

Write `get_or_default(value, default)` that returns `default` ONLY when `value` is `None` — not when `value` is `0`, `''`, or an empty list. Use the correct operator, then print three calls to prove `0` and `''` are kept.

Continue learning

Previous: Primitive types: int, float, bool, str

Previous: Mutable vs immutable objects

Next: Shallow vs Deep Copy

Return to Python Roadmap