Learning Goals
3 minBy the end of this lesson you can:
- Compare two strings case-insensitively using
.lower()or.upper()— so"Yes","YES"and"yes"all count as the same answer. - Swap pieces of a string with
.replace(old, new)and count occurrences with.count(sub). - Explain in your own words why
greeting.upper()does not changegreeting— strings are immutable, and clean up messyinput()with.strip().
Warm-Up
5 minTwo lessons ago we hunted bugs in a list lab. Today we switch from lists of items to lists of letters — strings. Same dot-method idea (my_list.append(...), my_list.sort()) but on text.
Quick predict-the-output puzzle. What does Python print?
greeting = "Selamat Pagi" shout = greeting.upper() print(shout) print(greeting)
Show the answer
SELAMAT PAGI Selamat Pagi
Surprised? greeting stays exactly the same. .upper() didn't shout at the original string — it built a brand-new string and handed it back. We caught that new string in shout. This is the single most important rule in today's lesson, and we'll come back to it.
New Concept · Strings Have Methods Too
14 minThe dot tells a string what to do
You've seen the dot before — menu.append("teh tarik"), numbers.sort(). Strings have their own family of dot-methods. The shape is always the same:
some_string.method_name(arguments)
Today's four headline tools:
.upper()— give me a SHOUTING copy..lower()— give me a quiet, lowercase copy..replace(old, new)— give me a copy witholdswapped fornew..count(sub)— tell me how many timessubappears.
upper and lower — case-blind comparisons
The classic bug: you ask the user a yes/no question, but they type "Yes" with a capital Y and your if answer == "yes" says no. Fix it by flattening the case before comparing:
answer = "Yes" if answer.lower() == "yes": print("Locked in!") else: print("No worries.") # → Locked in!
Notice we did not change answer. We made a temporary lowercase copy just for the comparison. The original answer is untouched — handy when you still want to greet the user with their original capitalisation later.
replace — swap one piece for another
order = "teh tarik, teh tarik, kopi" fixed = order.replace("teh tarik", "milo ais") print(fixed) # → milo ais, milo ais, kopi
Two things to spot. First, .replace swaps every match, not just the first. Second — same rule again — order itself is unchanged. We caught the new string in fixed.
count — how many times does X appear?
sentence = "kuih kuih kuih and more kuih" how_many = sentence.count("kuih") print(how_many) # → 4
.count is great for quick word-frequency games and for sanity-checking input — "does this phone number have exactly ten digits?"-style checks.
The immutability rule (the big one)
Strings are immutable. That's a fancy word meaning "can't be changed in place". Compare:
# lists CAN change in place dishes = ["satay", "rojak"] dishes.append("cendol") print(dishes) # → ['satay', 'rojak', 'cendol'] # strings CANNOT name = "aisyah" name.upper() print(name) # → aisyah (still lowercase!)
If you want the change to stick, catch the new string:
name = "aisyah" name = name.upper() print(name) # → AISYAH
The right-hand side name.upper() builds a new string; the = sign reassigns the label name to point at the new one. Old "aisyah" is forgotten.
Bonus tool · strip() for messy input
When a user types into input(), they sometimes leave a stray space — especially on tablets. .strip() trims whitespace from both ends:
answer = " yes " print(answer.strip()) # → yes
The everyday combo is .strip().lower() — clean, then flatten, then compare:
raw = input("Continue? ") if raw.strip().lower() == "yes": print("Onwards!")
String methods always return a new string. If you don't catch it with =, it vanishes. Lists mutate in place; strings build new copies.
Worked Example · Hafiz's Quiz Marker
12 minThe job
Hafiz is writing a one-question quiz: "What is the capital of Malaysia?". The right answer is Kuala Lumpur, but classmates might type "kuala lumpur", "KUALA LUMPUR", "Kuala lumpur", or even " Kuala Lumpur " with stray spaces. All of those should count as correct.
Step 1 · Ask the question
raw = input("What is the capital of Malaysia? ")
Step 2 · Clean the input
Strip the spaces and flatten the case — chain the methods left-to-right:
clean = raw.strip().lower()
Read this aloud: "Take raw, strip the edges, then lowercase the result." Each dot-call returns a new string, and the next dot acts on that.
Step 3 · Compare against a clean target
Flatten the right-hand side too, so we never have to remember whether we wrote "kuala" or "Kuala":
if clean == "kuala lumpur": print("Correct!") else: print("Not quite — the answer is Kuala Lumpur.")
Step 4 · Greet them with their original spelling
This is the bonus that immutability gives us for free — we still have raw in its original form:
print("You typed:", raw)
Putting it all together
Save as quiz_marker.py:
Code
# quiz_marker.py — case- and space-tolerant quiz raw = input("What is the capital of Malaysia? ") clean = raw.strip().lower() if clean == "kuala lumpur": print("Correct!") else: print("Not quite - the answer is Kuala Lumpur.") print("You typed:", raw)
Sample run
What is the capital of Malaysia? KUALA LUMPUR Correct! You typed: KUALA LUMPUR
Every quiz app, every chatbot, every login form silently runs .strip().lower() on what you type. It's the difference between software that feels friendly and software that fights you over a capital letter.
Try It Yourself
13 minThree tasks today, each a bit harder. Type each one, run it, and only peek at the hint if you're truly stuck.
Ask the user for a short slogan with input(). Print three things on three lines: the slogan as typed, the same slogan in ALL CAPS, and the same slogan in all lowercase. Confirm that the original is still unchanged at the end.
Hint
slogan = input("Type a slogan: ") print(slogan) print(slogan.upper()) print(slogan.lower()) print("Original:", slogan)
The whole point is the last line — slogan still says exactly what the user typed. .upper() and .lower() never touched it.
Set line = "Aisyah loves nasi lemak". Use .lower() and four calls to .count(...) to count how many times each vowel — a, e, i, o — appears. Print each total on its own line.
Hint
line = "Aisyah loves nasi lemak" clean = line.lower() print("a:", clean.count("a")) print("e:", clean.count("e")) print("i:", clean.count("i")) print("o:", clean.count("o"))
Why .lower() first? Because .count("a") alone would miss the capital A in "Aisyah". Flatten first, count second.
Start with review = "The kuih was awful, simply awful.". Use .replace to swap every "awful" with "****". Print the censored sentence and use .count on the original review to tell the user how many words were censored.
Hint
review = "The kuih was awful, simply awful." censored = review.replace("awful", "****") hits = review.count("awful") print(censored) print("Words censored:", hits)
Notice we count on review, not on censored — after the replace there are zero "awful"s left to find. This is exactly the immutability trick paying off: both versions of the string are still available.
Mini-Challenge · Daniel's Broken Greeter
8 minDaniel wrote a tiny greeter that's meant to print HELLO, AISYAH! no matter how the user types their name. He's convinced his code is correct, but the output is always lowercase. Read his script greeter.py below and fix it.
# greeter.py — Daniel's buggy greeter
name = input("Your name: ")
name.upper()
print("hello, " + name + "!")Your fixes should make the script:
- Print the greeting in ALL CAPS — both the word
HELLOand the name. - Still work even if the user types
" aisyah "with stray spaces. - Use a single
printat the end.
Stretch. If the user just presses Enter without typing anything (empty string), print NO NAME GIVEN instead.
Show one possible fix
# greeter.py — fixed name = input("Your name: ") name = name.strip().upper() if name == "": print("NO NAME GIVEN") else: print("HELLO, " + name + "!")
Daniel's bug was the classic immutability trap — name.upper() built a SHOUTING copy and then threw it away because he didn't catch it. The fix is one tiny name = in front. Chaining .strip().upper() handles the spaces and the case in one tidy line.
Recap
3 minStrings have dot-methods just like lists do, but with one giant difference — they never change the original string, they return a brand-new one. .upper(), .lower() and .strip() are the trio for cleaning user input; .replace swaps pieces; .count tallies. If you want a change to stick, assign the result back with =.
Vocabulary Card
.upper()/.lower()- Returns a new string with every letter shifted to upper- or lower-case. Original string is unchanged.
.replace(old, new)- Returns a new string with every occurrence of
oldswapped fornew. Doesn't touch the original. .count(sub)- Counts how many times
subappears in the string. Returns a whole number. .strip()- Returns a new string with leading and trailing whitespace removed. Workhorse for cleaning
input(). - immutable
- Cannot be changed in place. Strings are immutable; lists are mutable. To "change" a string, build a new one and reassign.
- method chaining
- Calling one method on the result of another, e.g.
raw.strip().lower(). Reads left-to-right.
Homework
4 minCreate a new file order_checker.py — a kantin order checker that's tolerant of messy typing.
- Ask the user with
input():What would you like to order?and save the raw answer in a variable calledraw. - Build a cleaned version with
raw.strip().lower()and store it inclean. - If
cleanequals"nasi lemak", printThat's our most popular dish!. - Otherwise, print
One order of <raw> coming up.using the originalrawso it keeps their typing flair. - Print one extra line:
Letters in your order:followed by the result ofclean.count("a") + clean.count("e") + clean.count("i") + clean.count("o") + clean.count("u"). That's a quick vowel counter.
Bring order_checker.py next class — in PY-L1-24 we'll dig inside strings with indexing and slicing.
Sample · order_checker.py
# order_checker.py — kantin order checker raw = input("What would you like to order? ") clean = raw.strip().lower() if clean == "nasi lemak": print("That's our most popular dish!") else: print("One order of " + raw + " coming up.") vowels = (clean.count("a") + clean.count("e") + clean.count("i") + clean.count("o") + clean.count("u")) print("Letters in your order:", vowels)
The non-negotiables: keep raw separate from clean, use .strip().lower() to clean, and use raw (not clean) when echoing the order back so the user's capitals survive. The vowel total just stitches five .count(...) calls together with + — no loops needed yet.