String Immutability and Memory Reallocation
Upon Completion of This Topic
You will understand why Python strings are immutable, how string operations work in memory, and how to avoid performance pitfalls when processing large strings.
Strings Cannot Be Modified
name = "Hello"name[0] = "h"# TypeError: 'str' object does not support item assignmentStrings in Python are immutable objects. Once created, their content cannot be changed. Any operation that appears to "modify" a string actually creates a new string.
name = "Hello"print(id(name)) # 4385123456
name = name.lower()print(id(name)) # 4385123520 β A different object!id() returns the memory address of an object. After lower(), the id has changed, meaning that the original string was not modified; instead, a new string was created.
Mutable vs. Immutable
| Type | Mutable | Example |
|---|---|---|
str | Immutable | "hello" |
tuple | Immutable | (1, 2, 3) |
int | Immutable | 42 |
float | Immutable | 3.14 |
list | Mutable | [1, 2, 3] |
dict | Mutable | {"a": 1} |
set | Mutable | {1, 2, 3} |
# List: Mutable β contents can be modified directlyitems = [1, 2, 3]items[0] = 99print(items) # [99, 2, 3] β same object, contents changed
# String: Immutable β cannot be modified, a new object is createdtext = "hello"new_text = text.replace("h", "H")print(text) # "hello" β original remains unchangedprint(new_text) # "Hello" β a new objectWhy Design for Immutability?
1. Hashable β Can Be Used as Dictionary Keys
# Strings can be used as dictionary keys (because they are immutable)scores = {"alice": 90, "bob": 85}
# Lists cannot be used as dictionary keys (because they are mutable)# scores = {[1, 2]: "value"} # TypeError: unhashable type: 'list'Dictionaries use the hash value of keys to find data. If a key changes, its hash value also changes, making it impossible to find the data. Only immutable objects can be hashed.
2. Safety β Safe to Share
def greet(name): greeting = "Hello, " + name return greeting
original = "World"result = greet(original)print(original) # "World" β the function cannot modify originalWhen you pass a string to a function, you don't have to worry about the original being changed within the function. Lists, on the other hand, can be modified within a function using .append().
3. Thread Safety
Multiple threads can simultaneously read the same string without any issues because no one can modify it, eliminating the need for locks.
String Interning
Python reuses frequently used strings. This is called interning.
a = "hello"b = "hello"print(a is b) # True β shares the same object!print(id(a) == id(b)) # True
c = "hello world!"d = "hello world!"print(c is d) # False or True β depends on the implementationShort and simple strings (like identifiers) are interned. Even if the same string appears multiple times, it only exists once in memory. This is possible because it's immutable β it can be shared without anyone being able to modify it.
# is vs ==a = "hello"b = "hello"print(a == b) # True β are the values the same?print(a is b) # True β is it the same object? (because of interning)
# Always use == to compare strings. is is unpredictablePerformance Pitfall β String Concatenation
# Bad: String concatenation in a loop β O(nΒ²)result = ""for i in range(10000): result += str(i) + "," # Creates a new string each time!In each iteration, a new string is created, and the previous content is copied. With 10,000 iterations, approximately 50 million string copies occur (1 + 2 + 3 + ... + 10000).
# Good: Use join β O(n)result = ",".join(str(i) for i in range(10000))join() calculates the final size in advance and allocates memory only once. The number of copies is reduced to O(n).
Performance Comparison
import time
# Method 1: += (slow)start = time.time()result = ""for i in range(100000): result += str(i)print(f"+=: {time.time() - start:.3f}s")
# Method 2: join (fast)start = time.time()result = "".join(str(i) for i in range(100000))print(f"join: {time.time() - start:.3f}s")
# Method 3: io.StringIO (large text)import iostart = time.time()buf = io.StringIO()for i in range(100000): buf.write(str(i))result = buf.getvalue()print(f"StringIO: {time.time() - start:.3f}s")
# Example result:# +=: 0.852s# join: 0.031s (27 times faster)# StringIO: 0.029sPractical Patterns
Ways to "Modify" Strings
text = "Hello, World!"
# Case conversiontext.upper() # "HELLO, WORLD!"text.lower() # "hello, world!"text.title() # "Hello, World!"
# Replacementtext.replace("World", "Python") # "Hello, Python!"
# Character-by-character modification (using a list)chars = list(text) # ['H', 'e', 'l', 'l', 'o', ...]chars[0] = 'h'result = "".join(chars) # "hello, World!"All of these methods return a new string. The original remains unchanged.
f-string Combination
name = "Hoon"age = 30greeting = f"Hello, {name}! You are {age} years old."f-strings internally create the final string at once. This is more efficient and readable than concatenating multiple strings with +.
Comparison with Other Languages
// Java β String is immutable, StringBuilder is mutable
String s = "Hello";
s = s + " World"; // Creates a new String object
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // Modifies the same object (mutable)
String result = sb.toString();// JavaScript β Strings are immutable (same as Python)
let s = "Hello";
s[0] = "h"; // Doesn't cause an error but is ignored
console.log(s); // "Hello" (not changed)// C β char array is mutable
char s[] = "Hello";
s[0] = 'h'; // OK β changes to "hello"In most modern languages (Python, Java, JavaScript, Go, C#), strings are immutable. This is a common design decision for safety and optimization. Java's StringBuilder, Go's strings.Builder, and Python's join() or io.StringIO serve the same purpose β a mutable buffer that assembles the final immutable string.
Key Takeaways
| Concept | Summary |
|---|---|
| Immutability | Content cannot be changed after creation. All "modifications" create new objects. |
id() | Checks the memory address of an object. |
| Interning | Identical strings are reused in memory (possible because they are immutable). |
join() | The correct way to concatenate strings. Avoid using += in loops. |
is vs == | is checks for object identity, == checks for value equality. Use == for strings. |
String immutability is a design decision in Python. It allows strings to be used as dictionary keys, ensures safe passing between functions, and enables memory optimization through interning. Remember this in practice: don't build strings with += in loops; use join() instead.