Why does `is` sometimes work on strings?

CPython interns short string literals that look like identifiers, so two of them share one object and is happens to be True. Build the same text at runtime and it is False. It is an optimisation, not a guarantee — compare strings with ==, and keep is for None.

Overview

What interning is

CPython keeps a table of strings and reuses the entry rather than allocating a second identical object. Identifiers, and literals in compiled code that look like identifiers, go in it automatically. The payoff is real: comparing interned strings can short-circuit on a pointer comparison, and attribute lookup does this constantly.

Immutability is what makes it legal. Sharing one object between unrelated pieces of code would be a disaster if either could modify it.

StringsConceptualMedium

Step through it

What to watch

  • Both rows hold the same text; only how it was built differs.
  • == is True in every frame. is is not.
  • Behaviour differs between the REPL and a script — which is the point.

Say this out loud

"That's interning - CPython reuses one object for short literals. It's an implementation detail, so I compare with == and only use `is` for None."

Why does `is` sometimes work on strings?

Why does 'a' is 'a' return True, and why should you never rely on it?

Why it is a trap

The rules are not part of the language. They vary by CPython version, by whether the string was a literal in a compiled block, by whether it contains a space, and by whether the constant folder saw it. The REPL compiles line by line and a script compiles as a unit, so the same code can behave differently in the two.

Code that uses is on strings therefore works in testing, works in the REPL, and fails on the one input that was built by concatenation. That failure looks like a logic error, not a comparison error, which is why it takes so long to find.

The rule, and the exception

Use == for strings, always. Use is only for singletons — None, True, False — where identity is the intended test.

There is one legitimate use of interning: sys.intern(s), called deliberately on a large set of repeated strings such as parsed field names, cuts memory and speeds up dictionary lookups. That is opting in, which is different from relying on a coincidence.

Run it in Python

Four ways of producing the same five characters, with identity and equality printed for each. Some of the is results may differ on your interpreter — that is the lesson, not a bug.

interning.pyPython 3
Output

How the code works

  1. a is bTwo identical literals in the same compiled block share one object. This is the result people generalise from, and it is the narrowest case.
  2. d = "".join(parts)Built while the program runs, so the interner never sees it. Same characters, different object, is is False.
  3. e is gThe space is the difference. A literal with a space does not look like an identifier, so the rules that produced True above do not apply.
  4. sys.intern(d)The legitimate use: deliberately deduplicating a large set of repeated strings to cut memory and speed up dict lookups. Opting in is not the same as relying on an accident.

Change one thing

  • Move the comparisons into a function and call it. Compiling as one unit can change the answers — which is exactly why this is not something to build on.
  • Try 257 is 257 across a function boundary. Integers have the same cache, the same trap and the same rule.

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 3

Answer without scrolling back up.

  1. Why is 'hello' is 'hello' often True?

  2. 'hello world' is ('hello' + ' world') is usually False because:

  3. The only safe use of `is` is with:

Cheat sheet

Why does `is` sometimes work on strings?

CPython interns short string literals that look like identifiers, so two of them share one object and is happens to be True. Build the same text at runtime and it is False. It is an optimisation, not a guarantee — compare strings with ==, and keep is for None.

INTERVIEW · vizlearn.in/interview/string-interning-and-the-is-operator.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.