`is` vs `==`, and why 257 is not 257

== asks "same value", is asks "same object". They coincide for small integers and short strings because CPython caches those objects, and they coincide for repeated literals because the compiler folds them — two different accidents, neither of which the language promises. Use is only for singletons.

Overview

The question, and what it is testing

== asks "same value", is asks "same object". They coincide for small integers and short strings because CPython caches those objects, and they coincide for repeated literals because the compiler folds them — two different accidents, neither of which the language promises. Use is only for singletons.

Interviewers use this one because the answer separates people who have read the language from people who have only used it. Nothing here is obscure; all of it is observable, which is what the editor below is for.

Python semanticsConceptualMedium

Step through it

What to watch

  • int('256') twice gives one object; int('257') gives two.
  • Writing 257 twice as literals gives one object again — that is the compiler, not the cache.
  • For lists, == and is never agree unless you aliased deliberately.

Say this out loud

"== compares values through __eq__; is compares object identity. They agree on small ints and interned strings because CPython caches them, which is an implementation detail I would never rely on. I use is only for None, True, False and sentinel objects."

`is` vs `==`, and why 257 is not 257

What is the difference between is and ==, and why does `a is b` sometimes surprise you with integers?

Run it

The cache boundary, the folding that hides it, and the nan case where equality and identity openly disagree.

The cache boundary - and the constant folding that hides it.

1Python
Output

For anything mutable the two never agree unless you aliased on purpose.

2Python
Output

And the case where the two operators openly disagree.

3Python
Output

Two different questions

a == b calls a.__eq__(b), which a type defines however it likes. a is b compares the addresses of the two objects and cannot be overridden by anything.

So == is a question about values and is is a question about storage. Confusing them usually works, which is the problem: the code passes its tests on small inputs and fails on a value that happens to fall outside a cache.

The small-integer cache, and the folding on top of it

CPython pre-creates every integer from −5 to 256 at startup and hands out the same object whenever one is needed. So int('256') is int('256') is True and int('257') is int('257') is False.

There is a second, separate mechanism that makes this hard to demonstrate. Constants written in one code block are folded by the compiler, so e = 257; f = 257 puts one constant in the code object and e is f is True — nothing to do with the cache, and it is why the same experiment gives different answers in a script and at a REPL prompt.

Short strings that look like identifiers get the same treatment through interning. The lesson is not the boundaries; it is that these are decisions an interpreter is free to change.

Where is belongs

Three cases, and essentially nothing else.

Singletons. x is None, x is True, x is False. There is exactly one of each, so identity is the correct test and it is faster than == besides.

Sentinels. _MISSING = object(), then if arg is _MISSING. The whole point of the object is that nothing else can be it.

Genuine aliasing questions. "Are these two names the same list?" is an identity question, and it is what you want when checking whether a caller handed you the object you already hold.

The follow-up: == that disagrees with itself

"Can x == x be False?" Yes. float('nan') == float('nan') is False by IEEE rule, and so is nan == nan for the same object — while nan is nan is True. That is the cleanest possible demonstration that the two operators are answering different questions, and it is why x in [float('nan')] can be True: the container check tries is first as a shortcut.

What to say out loud

== compares values through __eq__; is compares object identity. They agree on small ints and interned strings because CPython caches them, which is an implementation detail I would never rely on. I use is only for None, True, False and sentinel objects.

Then stop. The commonest failure on a question like this is answering it correctly in one sentence and then talking for another minute until something wrong comes out.

What to notice while it runs

  • int('256') twice gives one object; int('257') gives two.
  • Writing 257 twice as literals gives one object again — that is the compiler, not the cache.
  • For lists, == and is never agree unless you aliased deliberately.

Edge cases to raise

Volunteering these is most of what separates a correct answer from a good one.

The cache boundaries are not a language guarantee. PyPy, MicroPython and a future CPython are free to differ, so any behaviour that depends on them is a bug that happens to pass today.

x is not None reads better than not x is None and is the same operator. Worth using, because the second form invites a misparse.

Do not use is to test for emptiness or truth. if x is True fails for 1, for a non-empty list, and for anything with __bool__ - which is almost certainly not what you meant.

The follow-ups interviewers ask

"Should you ever use is on strings?" No, outside of sentinels. Interning makes it appear to work for short literals and it stops working for anything computed at runtime, which is the worst possible failure pattern - correct in development, wrong on real input.

"What about == on floats?" A different trap with the same shape: 0.1 + 0.2 == 0.3 is False. Use math.isclose, and mention it unprompted if the question involves numeric comparison at all.

"How do you make == work on your own class?" Define __eq__ - and then __hash__, because defining __eq__ alone sets __hash__ to None and makes instances unhashable. Two objects that compare equal must hash equally, or they break dictionaries. @dataclass(frozen=True) generates both correctly.

Common wrong answers

"is is faster, so use it for comparisons." It is faster, and it answers a different question. Speed is not a reason to ask the wrong thing.

"Small integers are interned up to 1000." −5 to 256. Quoting a wrong boundary confidently is worse than saying "a small range, and I would not depend on it".

"== always calls __eq__." Container membership checks identity first as a shortcut, which is why nan in [nan] is True while nan == nan is False.

Recap in one screen

  • int('256') twice gives one object; int('257') gives two.
  • Writing 257 twice as literals gives one object again - that is the compiler, not the cache.
  • For lists, == and is never agree unless you aliased deliberately.
  • The one-line answer: == asks "same value", is asks "same object".
  • Worth trying: Change 257 to 256 in the int() lines and watch is flip. Then try -6, which is one below the cache at the other end.
  • Worth trying: Compare "hello" is "hello" with "hello world" is "hello world". Identifier-like strings are interned; ones with a space often are not.

How the code works

The cache boundary, the folding that hides it, and the nan case where equality and identity openly disagree.

How the code works

  1. int("256") / int("257")Built at runtime so the compiler cannot fold them. This is the only reliable way to see the cache boundary.
  2. e = 257; f = 257True, and not because of the cache. One constant in the code object serves both names, which is why this experiment behaves differently in a file and at a prompt.
  3. MISSING is object()False: every object() call makes a new one. That is exactly the property a sentinel needs.
  4. nan in [nan]True while nan == nan is False, because in short-circuits on identity. The two operators are answering different questions and here you can see both answers at once.

Change one thing

  • Change 257 to 256 in the int() lines and watch is flip. Then try −6, which is one below the cache at the other end.
  • Compare "hello" is "hello" with "hello world" is "hello world". Identifier-like strings are interned; ones with a space often are not.

Where this runs

Real CPython, compiled to WebAssembly and running on your own machine — nothing is uploaded. The first run takes a few seconds while the interpreter downloads; after that it is immediate. Need more room, or want to paste your own attempt? Use the Python compiler.

Check yourself

0 of 4

Answer without scrolling back up.

  1. What does `is` compare?

  2. Why is int('257') is int('257') False?

  3. Why does e = 257; f = 257; e is f give True?

  4. When should you use `is`?

Cheat sheet

`is` vs `==`, and why 257 is not 257

== asks "same value", is asks "same object". They coincide for small integers and short strings because CPython caches those objects, and they coincide for repeated literals because the compiler folds them — two different accidents, neither of which the language promises. Use is only for singletons.

INTERVIEW · vizlearn.in/interview/is-versus-equals.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.