Skip to Content
Course content

323: String Interning and Identity

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was digging through a codebase recently and stumbled across a bug that looked absolutely impossible. Someone was using the is operator to compare two strings, and it worked perfectly in their local tests, but failed randomly in production. When I first saw it, I thought, "Who does that? Just use ==." But it got me thinking about why it worked at all, and that leads us directly into the weird world of string interning.

The Difference Between Same and Identical

First, let's clear the air on == versus is. == checks for equality—do these two things have the same value? is checks for identity—are these two things actually the exact same object in memory?

Let's try a quick experiment. I'll define two strings that are identical literals.

# Let's use a common config key as our example
key1 = "MAX_CONNECTION_RETRIES"
key2 = "MAX_CONNECTION_RETRIES"

print(key1 == key2)  # True
print(key1 is key2)  # True

Wait. Why is is True? I just defined two different variables. You'd expect Python to create two different string objects in memory, right? But it didn't. Python noticed these were the same constant string, so it just pointed both variables to the same memory address to save space. This is called interning.

When the Magic Stops Working

Now, here is where the bug I found actually lives. Interning doesn't happen for every string. It usually happens for constants that look like identifiers (letters, numbers, underscores). But if we build a string dynamically at runtime, Python's "automatic" interning often takes a backseat.

Let's try to build the same string using concatenation:

part1 = "MAX_CONNECTION"
part2 = "_RETRIES"
key3 = part1 + part2

print(key3 == key1)  # True (The values are the same)
print(key3 is key1)  # False (The identity is different!)

This is the "gotcha." To the human eye, key3 and key1 are identical. == tells us they have the same content. But is tells us they are different objects living in different spots in RAM. If you're using is for logic in your app, your code will pass when you use hardcoded strings but crash the moment those strings are loaded from a file or concatenated together.

Taking Control with sys.intern

You might be wondering: "If interning saves memory, why doesn't Python just do it for everything?" Well, checking every single string created during a program's lifecycle against a global table of interned strings would actually slow the program down. There's a trade-off between memory and CPU.

However, if you know you have a set of strings that will be repeated thousands of times (like keys in a massive dictionary of configuration settings), you can force Python to intern them using the sys module.

import sys

# Dynamic strings that aren't automatically interned
dynamic_key_a = "DATABASE_URL"
dynamic_key_b = "".join(["DATABASE", "_", "URL"])

print(dynamic_key_a is dynamic_key_b)  # False

# Now we force them into the intern table
interned_a = sys.intern(dynamic_key_a)
interned_b = sys.intern(dynamic_key_b)

print(interned_a is interned_b)  # True

By using sys.intern(), we're telling Python: "I don't care how this string was made; if you already have a copy of this value in the intern table, use that one." This is a powerful optimization for memory-heavy applications, but as a general rule of thumb for your daily coding: unless you are specifically optimizing for memory or working with singletons, stick to == for strings. It'll save you from the kind of debugging nightmare I had to deal with last week.




📋 Practical Task

Fixing the Identity Crisis in a Config Validator

You've been handed a piece of code for a configuration validator. The original author tried to be "clever" by using identity checks (is) for performance, but it's currently failing because the configuration keys are being read from a simulated file (as a list of strings) rather than being hardcoded.

Your Goal: Modify the validate_key function so that it correctly identifies the keys using identity (is), but ensure the strings are properly handled so that the identity check actually works.

import sys

def validate_key(input_key):
    # The author wants to use 'is' for a micro-optimization
    # but it's currently returning False for dynamic strings.
    TARGET_KEY = "API_VERSION_SECRET"
    
    # TODO: Ensure input_key is interned before the comparison
    if input_key is TARGET_KEY:
        return "Access Granted"
    else:
        return "Access Denied"

# Simulated input from a config file (these are dynamic strings)
user_input = "".join(["API", "_VERSION", "_SECRET"])

print(f"Result: {validate_key(user_input)}") 
# This currently prints "Access Denied". 
# Make it print "Access Granted" by fixing the function.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.