Skip to content
DevMeme
5125 of 7435
Python's Identity Crisis: The Peculiar Case of Integer Caching
Languages Post #5610, on Oct 29, 2023 in TG

Python's Identity Crisis: The Peculiar Case of Integer Caching

Why is this Languages meme funny?

Level 1: One Toy or Two?

Imagine a teacher has a box of special toys numbered 1 up to 256. There’s only one toy for each number in that box. So if one child asks for toy number 256 and another child also asks for toy 256, the teacher gives them the same exact toy from the box. Later, the kids realize they literally had the same toy (one shared item). Now, if the kids ask for toy number 257, the teacher doesn’t have that one pre-made in the box. She makes a new toy 257 for the first child, and when another child asks for 257, she makes another separate toy 257 just for that child. Those two toys both have “257” written on them and look identical, but they are actually two different physical toys. Each kid is holding their own copy. So when they compare, they say, “these aren’t the same object, just two copies that look the same.”

The joke here is that Python behaves like that teacher. For certain small numbers, everyone gets one shared object (so it really is the exact same thing each time), and for bigger numbers, each request gets a brand-new object (so you end up with duplicates that are only superficially the same). It’s funny because you wouldn’t expect the rules to suddenly change after a particular number — that little surprise makes us laugh at how the computer handles things behind the scenes.

Level 2: Identity vs Equality

For a newer Python developer, the key lesson here is understanding identity versus equality. The operator == checks if two values are the same in value, but is checks if they are actually the same object. Think of each object in Python having a unique ID or address in memory. So a is b will only be True if a and b are literally references to the exact same thing internally.

In the meme’s code example:

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

a = 257  
b = 257  
print(a is b)   # False  

The result is surprising at first. Why does 256 give True for a is b but 257 gives False? It turns out Python has a little optimization: it interns (reuses) some integers. Integers from -5 up to 256 are kept in a special cache inside the Python interpreter. So when you do a = 256 and b = 256, Python doesn’t create two separate 256 objects in memory. It gives both a and b a reference to the exact same pre-existing 256 object. They genuinely are the same object, hence a is b evaluates to True.

However, 257 is not in that pre-made cache. So a = 257 creates a new integer object for 257, and b = 257 creates another new integer object. Now a and b hold two different objects that happen to have the same numeric value. They look equal (a == b would be True because 257 equals 257), but they are not the very same object. That’s why a is b comes out False for 257 – each variable has its own 257 object.

The important takeaway is that in Python, you should use == to compare values in most cases, especially for numbers. The is operator is a special tool that checks identity, which is only useful when you actually want to know if two references are to the one single object (for example, checking if x is None: is correct, because there’s only one None object). The meme is a playful demonstration of what can happen if you mix these up. Python’s small integer caching is a behind-the-scenes feature to make number handling faster, but if you weren’t aware of it, seeing True then False like this can be pretty confusing!

Level 3: Ghosted by 257

From a seasoned Python developer’s perspective, this meme is poking fun at a classic gotcha: using the Python is operator where == should be used. The code snippet in the image looks innocent — just two variables holding the same number — yet it prints True for 256 and False for 257. Why the inconsistency? It’s highlighting Python’s small integer interning in a tongue-in-cheek way.

In Python, is checks object identity (think: are these the exact same object in memory?), while == checks value equality (do the objects have the same value?). Ideally, you wouldn’t use is to compare plain numbers at all, but here someone did, and the results are weird. For 256, Python’s small integer singleton cache kicks in, so a and b actually reference one shared object. It’s as if the number 256 is a single common instance. Thus a is b unexpectedly returns True, leading you to believe everything is fine. But for 257, Python creates two distinct int objects, so a is b comes back False — surprise! The second 257 is not the same object as the first one, essentially "ghosting" any assumption of identity.

This humor resonates with experienced devs because we’ve seen it in real code. It’s that sneaky language quirk where someone writes something like:

status_code = 200  
if status_code is 200:  
    print("OK")  

It works by accident (200 is interned, so it passes the is check), but if they later change the code to:

status_code = 500  
if status_code is 500:  
    print("Error")  

nothing prints – the check fails even though status_code == 500. That’s a head-scratcher if you don’t know about the interned ints! The meme’s text “What is wrong?” perfectly captures that perplexity. It’s the kind of bug that sneaks into codebases when a developer mistakenly uses is for integers or strings instead of ==. Seasoned Pythonistas know to compare numbers with == (and reserve is for special cases like checking against None or other singletons).

The punchline in the title, “Python’s interned ints: why 256 is ‘is’, but 257 ghosts you,” plays on the idea that Python promises to be simple and explicit, yet here it has a hidden optimization that can trip you up. It’s not a bug in Python itself – it’s a deliberate performance feature – but it can manifest as a bug in your code if you’re not careful. For a senior dev, this meme is both funny and a gentle reminder: even in a clean high-level language, under-the-hood details (like object caching) can lead to spooky surprises.

Level 4: Cache Miss at 257

Deep inside CPython (the standard Python interpreter) there’s a cunning performance trick at play. Python represents every integer as an object (PyLongObject in C), which normally means each time you create an integer, you allocate a new chunk of memory. However, creating and destroying tons of small numbers (like loop counters, array indices, etc.) would be inefficient. So CPython uses a small integer cache (a form of interning, inspired by the flyweight pattern): it pre-allocates all integers in the range -5 to 256 at startup and keeps them around for reuse.

This means whenever your code needs an integer in that range, Python doesn’t create a new object – it hands you a reference to an existing one. For example, when you do a = 256 and then b = 256, under the hood both a and b point to the exact same PyLongObject in that cache. They have the same memory address (you can verify with id(a) and id(b)), so a is b rightly returns True. The is operator in Python is essentially a pointer comparison: it checks if two references are to the same object in memory. Here, they are.

But if you assign a = 257 and b = 257, Python goes down the normal path: 257 is outside the cached range, so a brand new integer object is created for each assignment. Now a and b refer to two different objects that just happen to both represent the numeric value 257. Their id() values are different, so a is b comes out False. In other words, the second 257 doesn’t reuse the first one – there’s no interned object to grab – and the two 257s have distinct identities (the first 257 is left hanging, effectively ghosting you when you check identity).

The choice of caching -5 through 256 is an implementation detail of CPython (historically chosen to cover common small numbers; 256 is 2^8, a nice round binary boundary). It’s a trade-off: caching a wider range would consume more memory for diminishing speed returns. Other Python interpreters (PyPy, Jython, etc.) may intern differently, but this range has been a long-standing default. Importantly, this behavior is a CPython performance feature, not a guaranteed language rule. Code that relies on it can become confusing. The meme cleverly exploits this quirk: what seems like identical assignments produce different outcomes for is because of Python’s hidden caching mechanism. This deep dive into Python’s memory management explains exactly why 256 is special here – and why 257 behaves like a completely separate ghost each time.

Description

The image presents a programming quiz-style meme titled 'What is wrong?'. It shows two blocks of Python code with their respective outputs. The first block assigns the integer 256 to variables 'a' and 'b', and the expression 'a is b' evaluates to 'True'. The second block assigns 257 to 'a' and 'b', but this time 'a is b' evaluates to 'False'. Both blocks contain the watermark '#clcoding.com'. The technical context behind this is a CPython optimization where a range of small integers (from -5 to 256) are pre-allocated and cached. The 'is' operator checks for object identity (whether two variables point to the same memory location), not just value equality. For 256, both 'a' and 'b' point to the same cached object, so 'a is b' is True. For 257, which is outside this range, Python creates two separate objects in memory, making 'a is b' False. This behavior is a classic 'gotcha' that contradicts Python's reputation for being straightforward, as noted by the post's caption

Comments

113
Anonymous ★ Top Pick The Zen of Python says 'There should be one-- and preferably only one --obvious way to do it.' CPython's integer cache then whispers, '...unless the number is bigger than 256.'
  1. Anonymous ★ Top Pick

    The Zen of Python says 'There should be one-- and preferably only one --obvious way to do it.' CPython's integer cache then whispers, '...unless the number is bigger than 256.'

  2. Anonymous

    256 gets the VIP singleton pass in CPython’s small-int pool; 257 shows up and your ‘is’ test fails harder than your promised five-nines SLA

  3. Anonymous

    After 15 years of Python, you still explain to juniors that 'is' checks if two variables are the same person at a party, while '==' checks if they're wearing the same outfit. Then CPython's integer caching from -5 to 256 shows up like that one friend who insists on being a singleton at every gathering

  4. Anonymous

    Ah yes, the classic 'is' vs '==' interview trap that separates those who've read CPython source from those who just write Python. Nothing says 'senior engineer' quite like knowing that -5 to 256 live in a special VIP lounge while 257 has to wait outside like a peasant. It's Python's way of teaching us that identity is just a social construct... until you hit production and wonder why your cache key comparison works in dev but fails with real data. Pro tip: if your code's correctness depends on whether integers are the same object in memory, you're not writing Python - you're writing a very expensive philosophy dissertation on the nature of sameness

  5. Anonymous

    If your dedup logic uses 'a is b', congratulations - your correctness now depends on CPython’s small-int cache; your SLA is fine until a customer orders item 257

  6. Anonymous

    Python's small int cache: the singleton pattern 256 lives for, but 257 has to rent its own object - talk about class discrimination

  7. Anonymous

    Relying on CPython’s small‑int cache for correctness is how ‘is’ becomes a portability bug - works on your laptop, detonates on PyPy

  8. @rostopiradv 2y

    wtf?

  9. @affirvega 2y

    maybe 256 is byte and integer size of a byte is stored as value?

    1. @qwertymoll 2y

      No, byte max value is 255

      1. @affirvega 2y

        then i have no clue

    2. @RiedleroD 2y

      numbers from some negative number to 256 are singletons, for performance reasons. This is literally UB too, since this is only an implementation peculiarity in CPython.

  10. @Darkangeel_hd 2y

    i think it has to do with the id of values

  11. @waifu_anton 2y

    Caching, I guess. Numbers up to 256 are cached and returned as a single data type. 257 is not cached, so it is two data types in memory. At least that's how it works in Java

    1. @trainzman 2y

      yes

      1. @Darkangeel_hd 2y

        can you gives us the whole story pls ?

        1. @trainzman 2y

          https://www.codementor.io/@arpitbhayani/python-caches-integers-16jih595jk I guess this one should help

          1. @Darkangeel_hd 2y

            thanks

    2. @symptom9 2y

      This is true

  12. @Ranchonyx 2y

    This is on the same level as JavaScript fuckery

    1. @deerspangle 2y

      Not even slightly, JS is another level

      1. @Ranchonyx 2y

        I'd say this one is more obscure than JS magicks. All the crazy stuff happening in JS is well documented at least.

        1. @Araalith 2y

          JavaScript's pretty simple, until you mix numbers, objects and arrays with ToString(). Who even thought that was a good idea?

          1. @purplesyringa 2y

            and valueOf

          2. @sylfn 2y

            who even thought using JavaScript is a good idea?

          3. @sylfn 2y

            i -= -1...)

            1. @Araalith 2y

              What? -=-1 is equal to +=1

              1. @sylfn 2y

                what about types

                1. @Araalith 2y

                  There is TS to fix it.

                2. @Araalith 2y

                  Also: char * s = "12"; s-=-1; printf("%s", s);

                  1. @affirvega 2y

                    2??

    2. @RiedleroD 2y

      > checks for reference equality rather than value equality in a built-in datatype that isn't meant to be used like an object > wonders why UB is happening no, this isn't js-like fuckery

      1. @purplesyringa 2y

        This is not UB though

  13. @Darkangeel_hd 2y

    from 0 to i guess 256 each number has it's own id

  14. @trainzman 2y

    also not from 0

  15. @trainzman 2y

    but from -5

    1. @Darkangeel_hd 2y

      thanks for the correction

      1. @trainzman 2y

        it also sounds so funny

  16. dev_meme 2y

    is is used in comparisons to check whether the variables refer to the same address in memory. As the numbers up to 256 are cached in memory, it produces this counterintuitive result.

  17. @trainzman 2y

    "We cache -4"

  18. @cmdrosmium 2y

    the only reason you want to use is operator is when you want to check variable for nullability (if x is None:) I dunno is there any other scenario when you really need to test variables by their memory references

    1. @trainzman 2y

      type(X) is Y

      1. @cmdrosmium 2y

        if isinstance(X, Y): is better in most cases, because it works for subclasses too

        1. @Assarbad 2y

          And multiple types at once (second argument can be tuple of types).

    2. @gizlu 2y

      I also used x is False to avoid coercion of 0 and "" to False

  19. @anarchist47 2y

    https://ideone.com/EgC18t

    1. @RiedleroD 2y

      maybe that's not CPython. It is UB after all

  20. @Ranchonyx 2y

    My opinion may be biased due to me working with that thing on a daily basis and having gotten used to the black magic.

    1. @id8seq 2y

      Just don't use "is", You will almost never face this behavior in the code, in contrary to JS weirds

      1. @Ranchonyx 2y

        What exactly does "is" do then? Apparently it's more than comparison by value?

        1. @id8seq 2y

          It's comparison of address in memory. 'id(a) == id(b)' Though objects may be equal, but if in memory they are different you get false. If you want to compare objects you use "=="

          1. @Ranchonyx 2y

            Thanks for letting me know

  21. @id8seq 2y

    Similar thing happens for other objects as well, for example for the string. Take the quiz: https://github.com/zshimanchik/python_core_tutorial/blob/master/02_int_caching.ipynb

  22. @a125x_a 2y

    bro: literally doesn't understand what "is" does also bro: wtf i don't understand what "is" does 🤯 omg so unintuitive

  23. @pe_che 2y

    Um?

    1. @Assarbad 2y

      Wouldn't be surprised if this were both configuration and implementation specific. Essentially the "meme" is a nice example for why you should know operators and types and such and pick the right ones ... (I guess in all languages)

    2. @colllapse 2y

      print python version. reproduced on 3.11.2

      1. @RiedleroD 2y

        I checked, it depends on how you call it. https://t.me/devs_chat/98827

        1. @RiedleroD 2y

          i.e. in a file a is b → True and inside the interactive shell, a is b → False

          1. @colllapse 2y

            if repl than yes, otherwise it's done bugless i assume, yes

            1. @RiedleroD 2y

              wdym bugless, all of this is intended behaviour

            2. @endisn16h 2y

              *me sitting here for two straight minutes, trying to figure out wheres REPL part came from*

              1. @colllapse 2y

                you have three options - 1. write to a file and exec it, 2. run REPL and put them there or 3. pass some code as argument to the interpreter

                1. @endisn16h 2y

                  ik, just had a lil brain melt

  24. @beton_kruglosu_totchno 2y

    "caching numbers" makes me vomit

  25. @pe_che 2y

    Ok, I see

  26. @purplesyringa 2y

    Honestly, you're pretty damn lucky if you've only encountered this symptom in Python, not due to pointer provenance

  27. Yuri 2y

    "is" checking if it is the same object... Up to 256 ints are just a pointer, so it is literally points at the same memory cell...

    1. @RiedleroD 2y

      all ints are objects in python, but they're only singletons up until 256 (plus some negative numbers)

  28. @chupasaurus 2y

    Like writing to a log file with rotation without losing the output?

  29. @chupasaurus 2y

    Mine is too a bit. Since JS itself wasn't meant to be working with filesystems, NodeJS and other runtimes followed that by not supporting syscalls which track FS nodes.

  30. @RiedleroD 2y

    don't compare numbers with is >:I

  31. @karumsenjoyer 2y

    maybe because usually such code is written only by some software engineering babies or fools

    1. @ZgGPuo8dZef58K6hxxGVj3Z2 2y

      Wait till you realize that JavaScript uses 64 bit integers for everything, pointers, consts (like null, true, false, NaN, etc) and that objects are sometimes stored like vectors, sometimes as a fucking "butterfly" which is custom type that is a 32 bit pointer stored in a 64 bit integer and points in the middle of the array. The left side of the array stores values/pointers to the properties, and the right side stores array items if the object is also an array (which it is sometimes)

      1. @endisn16h 2y

        stop it you are scaring me

        1. @ZgGPuo8dZef58K6hxxGVj3Z2 2y

          That was not all

          1. @RiedleroD 2y

            oki imma leave now :)

  32. @ZgGPuo8dZef58K6hxxGVj3Z2 2y

    The problem is that you mixed tabs with spaces

  33. @ZgGPuo8dZef58K6hxxGVj3Z2 2y

    floating point numbers range from 0001 0000 0000 0000x to FFFE FFFF FFFF FFFFx

    1. @RiedleroD 2y

      what

    2. @purplesyringa 2y

      Wait wait isn't every bit sequence a valid fp number?

      1. @ColonelPhantom 2y

        I'm not sure about the trick described above, but there are many different NaN values which can be abused to store extra data for NaN

    3. @sylfn 2y

      where is it from

  34. @Nefrace 2y

    hmm

    1. @RiedleroD 2y

      still UB, it's not the job of python to make sure you don't do cursed shit with it

      1. @Araalith 2y

        Python exists because it babysits programmers. It's slow, hungry, and lacks power. And now you claim that it fails even in its sole purpose?

        1. @RiedleroD 2y

          python exists to be quick to code, not to babysit programmers.

          1. @Araalith 2y

            Do you think Python is quick? To me, it feels like it took the worst of both worlds: the clumsiness of compiled languages and the sluggishness of interpreted ones. Just compare it to Perl, for example, and you'll see how to achieve the same results with half the code. But sure, if by "quick" you mean "pull it from a library and pray," then you're correct... however from this perspective Bash even more "quick".

            1. @RiedleroD 2y

              it's not clumsy at all though bash is cursed

              1. @pixelsex 2y

                bash is the OG python

              2. @RiedleroD 2y

                and no, I barely ever use external libs for my python scripts

            2. @purplesyringa 2y

              Bash isn't as universal though

    2. @RiedleroD 2y

      in this case, it's because the parser somtimes likes to make two equivalent literals the same object, to save on instanciating overhead

      1. @RiedleroD 2y

        so it actually compiles to bytecode that looks something like _hidden = 257 a = _hidden b = _hidden

  35. @RiedleroD 2y

    256 is still part of the numeric range where python uses singletons

  36. @kuklochai 2y

    all I know is that baba is you

  37. @im_ali_pj 2y

    change your python

    1. @ColonelPhantom 2y

      What python is that? I tried it on python 3.10 and got the described result

      1. @im_ali_pj 2y

        i tried on 3.5 3.6 3.8 and 3.9

        1. @im_ali_pj 2y

          and 3.11

        2. @RiedleroD 2y

          try writing it to a file and executing it then, the precompile stage sometimes does a bit of optimization the interactive interpreter can't

  38. @colllapse 2y

    lol > This is guaranteed to be unique among simultaneously existing objects. such a scam

    1. @RiedleroD 2y

      ints aren't meant to be used like regular objects

  39. @colllapse 2y

    but that makes me wonder why its only 256 values

    1. @RiedleroD 2y

      because ints up to 256 are singletons (in CPython at least)

      1. @colllapse 2y

        i mean why not regular int but only char sized? everything bigger than byte eventually degrades to PyNumber?

        1. @RiedleroD 2y

          all python ints are objects. The interpreter just saves some time on creating lower ones, because they get used a lot in e.g. iterators and such

          1. @colllapse 2y

            I thought they start to become one after int32 boundaries passed. But I guess its only int16

            1. @RiedleroD 2y

              no, all of em are objects in python. They become slightly different objects at some point though, when values get too large (or small) they keep track of the value via a BigInt-ish structure internally.

              1. @RiedleroD 2y

                (iirc, afaik)

              2. @colllapse 2y

                I know that they become big int at some point (with enormous base as representation)

  40. @RiedleroD 2y

    it's an arbitrary decision. They could've made it 100 or 123. They still probably chose 256 because 2**8 is a nice number in computing

  41. @RiedleroD 2y

    ok I hate js as much as the next guy (or gal or nb pal) but if you modify your object inside its valueOf, that's your fault.

Use J and K for navigation