Skip to content
VLSI Mentor

Python Part I · Module 4 · Strings

Strings Do Not Change: Immutability

Every chapter of this module has shown you a method that gives back a new string rather than changing the one you had, and each time the point was noted and deferred to here. This is the chapter that explains it, and the explanation turns out to be one sentence from Module 2 applied to text: a string is an immutable value, so there is no operation that edits one. That single fact accounts for the TypeError you get when you assign to a character, for why join is better than repeated concatenation, and above all for the most expensive beginner bug in Python text handling — the line that cleans up a piece of text, produces the cleaned version perfectly, and then quietly throws it away.

Foundation12 min readPythonPart IFundamentalsStringsImmutability

Part I · Module 4 · Chapter 4.11 · Strings Do Not Change: Immutability

1. The Bug This Chapter Prevents

Start with the mistake, because it is the reason the chapter exists. Here is a script that intends to clean up a status word and does not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
line = "  PASS  "
line.strip()
print(repr(line))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'  PASS  '

Nothing happened. No error, no warning, no clue. The spaces are still there, and if the next thing the script does is compare line against "PASS", it gets False and the author spends twenty minutes staring at a strip() call that looks completely correct.

It is completely correct. It stripped the string and produced "PASS". Then, because nothing was done with that result, the result ceased to exist.

The fix is one character of difference:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
line = "  PASS  "
line = line.strip()
print(repr(line))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'PASS'

That is the whole practical content of this chapter. The rest is why it has to work this way, which is what stops you making the same mistake with a method you meet for the first time in three years.

2. Why There Is No "Edit" Operation

Chapter 2.13 divided Python's values into two kinds: those that can be changed after they are created, and those that cannot. A string is the second kind. The word is immutable, and it means exactly what it says — there is no operation anywhere in Python that modifies an existing string.

Not "it is discouraged". Not "the methods happen to return copies". There is no such operation, which is why the obvious attempt fails:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
text = "PASS"
text[0] = "F"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TypeError: 'str' object does not support item assignment

Chapter 4.4 showed you this and promised the explanation here. Read the message literally: this kind of value does not support having one of its items assigned to. Not "you got the syntax wrong" — the operation does not exist for strings.

Trying it with a slice gets you the identical message, which is itself informative:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
text = "PASS"
text[0:2] = "FA"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TypeError: 'str' object does not support item assignment

One character or several, the answer is the same, because the problem is not the amount you wanted to change. It is that changing is not a thing a string does.

So when you ask a string to do something to itself, the only thing it can give you is a different string:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
t = "PASS"
print(repr(t.lower()))
print(repr(t.replace("P", "F")))
print(repr(t))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'pass'
'FASS'
'PASS'

Two new strings produced, and t still referring to the original. Every method in Chapters 4.7 through 4.10 behaved this way, and now you can see it was not a series of independent design choices — it is the only behaviour available.

3. What "Changing" a String Actually Looks Like

You can of course get the text you want. You build it, rather than editing it. Slicing from Chapter 4.5 and concatenation from Chapter 4.3 are all you need:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
text = "PASS"
new_text = "F" + text[1:]
print(new_text)
print(repr(text))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
FASS
'PASS'

Read the second line carefully, because it is the model for everything:

  • text[1:] took a slice — the characters from position 1 to the end, "ASS". text was not touched; slicing reads.
  • "F" + "ASS" built a third string, "FASS".
  • new_text was given that string as its value.

Three strings existed by the end of that line and none of them was modified. The original "PASS" is exactly as it was, which the last line confirms.

If you do not want the original any more, point the same name at the new value:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
text = "PASS"
text = "F" + text[1:]
print(text)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
FASS

4. It Is the Name That Moved, Not the String

This is the part that separates understanding from memorising, and Chapter 2.3 built the model for it: a name refers to a value; it does not contain one.

So text = "F" + text[1:] does not modify anything. It computes a new string and then points the name text at it. The old string is simply no longer what that name refers to.

You can demonstrate that with two names and no new concepts:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a = "PASS"
b = a
a = a.lower()
print(a)
print(b)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
pass
PASS

Follow it line by line. b = a gave b the same value a had — both names now refer to the string "PASS". Then a = a.lower() produced a new string "pass" and pointed a at it. And b still says PASS.

If lower() had modified the string, b would have changed too, because b was referring to that very string. It did not change. That is the proof, and it needed nothing but printing.

This also settles the += question from Chapter 4.3:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
s = "a"
s = s + "b"
print(s)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ab

s += "b" is the same thing written shorter. It does not append to "a" — it cannot. It builds "ab" and rebinds s. That is why Chapter 4.3 said each step copies everything accumulated so far, and why Chapter 4.9's join is the right tool for many pieces: join builds the final string once instead of building every intermediate version along the way.

5. Where Does the Old String Go?

A fair question after Section 4. The honest Part I answer: once no name refers to a value, the value is unreachable and Python reclaims the space it occupied, automatically and without you arranging it.

That is genuinely all you need. There is no manual cleanup to write and no leak to worry about.

6. The Three Habits

Everything practical from this chapter reduces to three habits.

Store the result. Whenever a method gives you text you want, assign it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
raw = "  PASS  \n"
clean = raw.strip()
print(repr(clean))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'PASS'

Or rebind the same name, when you do not need the original:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
line = "  PASS  \n"
line = line.strip()
print(repr(line))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'PASS'

Both are correct. Use a new name when the original still matters — you may want the raw text for an error message — and rebind when it does not.

Or chain, when each step feeds the next and only the final result matters:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print(repr("  Pass \n".strip().lower()))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'pass'

Chapter 4.7 introduced chaining; now you know why it works. Each method returns a string, so the next method has a string to act on. Nothing is being modified at any point in the chain — three strings are produced and two are discarded on the way.

7. Predict, Run, Observe

Predict all five before reading on. Only one of these strings ends up changed.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
one = "  A  "
one.strip()
print(repr(one))

two = "  B  "
two = two.strip()
print(repr(two))

three = "C"
four = three
three = three.lower()
print(repr(four))

print(repr("  D  ".strip()))

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'  A  '
'B'
'C'
'D'

one is unchanged — the stripped value was discarded. two is stripped, because the result was stored. four is still 'C', because lower() produced a new string and only three was pointed at it. And the last line prints the stripped value directly without any name involved, which is perfectly valid — the value only needs to survive long enough to be printed.

If you predicted 'c' for four, you were treating lower() as something that modifies a string. Section 4 is the correction, and the three-line demonstration there is worth running yourself.

8. Exercises

Exercise 1 — find the thrown-away result. Each of these does nothing useful. Fix each with the smallest change:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a = "  x  "
a.strip()

b = "PASS"
b.lower()

c = "a\tb"
c.replace("\t", " ")

Exercise 2 — predict, then run. Give the exact output of each print, then verify:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
p = "PASS"
q = p
p = p.replace("P", "F")
print(p)
print(q)

Exercise 3 — build the change. Without using replace, produce "FASS" from "PASS" using only a slice and concatenation. Then do the same to produce "PASx" from "PASS".

Exercise 4 — read the error. Run text = "PASS" followed by text[3] = "x". Write down the exact error, then explain in one sentence what it says about strings rather than about your syntax.

Exercise 5 — the proof. Write the three lines that prove upper() does not modify a string, using two names and no new concepts. Say in one sentence what the output would have been if upper() did modify it.

Exercise 6 — new name or rebind. For each situation, say whether you would assign to a new name or rebind the old one, and why: (a) you need the cleaned line and will also report the raw line if something is wrong (b) you read a line, stripped it, and have no further use for the version with the newline (c) you are normalising a status word for one comparison and nothing else

Exercise 7 — connect it back. In three sentences, explain why immutability is the reason join is preferred over repeated += for assembling a large piece of text. Refer to what each step of += actually does.

Exercise 8 — the good side. Give one concrete reason immutability is useful rather than merely inconvenient. Your answer should mention something that cannot happen to a string you are holding.

9. Summary

  • A string is an immutable value. There is no operation in Python that modifies an existing string — not a discouraged one, none at all.
  • text[0] = "F" is TypeError: 'str' object does not support item assignment, and a slice assignment gives the identical message, because the problem is not how much you wanted to change.
  • Every string method returns a new string. That is not a design choice per method; it is the only thing available.
  • A method call alone on a line throws its result away. line.strip() by itself does nothing, silently. This is the most expensive beginner bug in Python text handling.
  • You cannot change a string; you can build a different one and choose what to call it. "F" + text[1:] is the pattern.
  • Assignment moves the name, not the string. text = text.lower() points text at a new value; any other name still refers to the old one — which is how you prove no modification happened.
  • s += "b" builds a new string and rebinds, which is why repeated concatenation rebuilds everything each step and join does not.
  • A value nothing refers to is reclaimed automatically. That is the whole of what you need to know about it.
  • Three correct habits: store the result under a new name, rebind the same name, or chain the calls.
  • Immutability is a guarantee, not just a nuisance — text you hold cannot be altered by anything else.

Every chapter so far has worked with text you already had. The last two build new text, with values placed inside it.

Where this fits

Part of the Python curriculum.