Python
is operator
integer comparison
unexpected behavior
Python programming

Why does Python's is operator behave unexpectedly with integers?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Python's is operator checks whether two variables point to the same object in memory — not whether they have the same value. With small integers (typically -5 through 256), CPython caches and reuses the same objects, so is returns True. With larger integers created at runtime, Python allocates separate objects, so is returns False even when the values are equal. This is an implementation detail of CPython's integer interning optimization, and understanding it prevents subtle identity-vs-equality bugs.

is vs ==

python
1a = 256
2b = 256
3print(a == b)  # True — same value
4print(a is b)  # True — same object (cached)
5
6a = 257
7b = 257
8print(a == b)  # True — same value
9print(a is b)  # False — different objects (not cached)

== compares values. is compares identity (memory address). For integers, always use == unless you specifically need identity checks.

CPython Integer Interning

CPython pre-allocates integer objects for the range -5 to 256 at startup. Every variable assigned a value in this range points to the same pre-existing object:

python
1import sys
2
3a = 100
4b = 100
5print(id(a) == id(b))  # True — same object
6
7a = 1000
8b = 1000
9print(id(a) == id(b))  # False — different objects
10
11# Verify the cached range
12for i in range(-10, 270):
13    a = i
14    b = int(str(i))  # Force a fresh conversion
15    if a is not b:
16        print(f"First non-interned integer: {i}")
17        break
18# Output: First non-interned integer: 257

The range -5 to 256 was chosen because these values appear frequently in typical programs (loop counters, small constants, return codes).

Compile-Time Constant Folding

Within a single compilation unit (module, function, or interactive statement), CPython may reuse the same object for identical integer literals even outside the interned range:

python
1def example():
2    a = 1000
3    b = 1000
4    print(a is b)  # True — same constant in one function
5
6example()
7
8# But across separate statements in the REPL:
9a = 1000
10b = 1000
11print(a is b)  # False — different compilation units

This is a compiler optimization, not a language guarantee. Different Python implementations (PyPy, Jython, IronPython) may behave differently.

String and Other Object Interning

The same identity gotcha applies to strings and other immutable types:

python
1# Short strings may be interned
2a = "hello"
3b = "hello"
4print(a is b)  # True (usually)
5
6# Strings with special characters are not
7a = "hello world!"
8b = "hello world!"
9print(a is b)  # False (usually, in the REPL)
10
11# None is always a singleton
12a = None
13print(a is None)  # True — correct usage of `is`
14
15# Booleans are singletons
16print(True is True)    # True
17print(False is False)  # True

is is the correct operator for None, True, and False because they are guaranteed singletons. For everything else, use ==.

When to Use is

python
1# Correct: checking for None
2if result is None:
3    print("No result")
4
5# Correct: checking for sentinel values
6_MISSING = object()
7
8def get(key, default=_MISSING):
9    value = lookup(key)
10    if value is _MISSING:
11        raise KeyError(key)
12    return value
13
14# Correct: checking type identity
15if type(x) is int:
16    print("Exact int type")
17
18# WRONG: comparing values
19if x is 42:      # Don't do this
20    pass
21if x == 42:      # Do this instead
22    pass

Common Pitfalls

  • Using is to compare integers or strings: is checks identity, not equality. a is 257 may be True or False depending on how the integer was created. Always use == for value comparison.
  • Relying on integer interning across implementations: The -5 to 256 caching range is a CPython implementation detail. PyPy, Jython, and other implementations may intern different ranges or no range at all. Code that depends on is for integers is non-portable.
  • Assuming compile-time folding is consistent: Within a single function, a = 1000; b = 1000; a is b may be True because the compiler reuses the constant. The same code split across REPL lines returns False. Never rely on this behavior.
  • Forgetting that is is correct for None: PEP 8 recommends if x is None rather than if x == None because None is a singleton and == can be overridden by custom __eq__ methods.
  • Confusing id() equality with is: id(a) == id(b) is not always equivalent to a is b for short-lived objects, because Python may reuse memory addresses after garbage collection. Use is directly for identity checks.

Summary

  • is checks object identity (same memory address); == checks value equality
  • CPython interns integers from -5 to 256 — is returns True for these values
  • Outside this range, separate integer objects are created and is returns False
  • Compile-time constant folding may cause is to return True within a single function even for large integers
  • Always use == for value comparison; reserve is for None, sentinels, and singleton checks
  • Integer interning is a CPython implementation detail — do not write code that depends on it

Course illustration
Course illustration

All Rights Reserved.