Intuition What this page is for
The parent note taught you what each method does. Here you will practise on every kind of input a string can throw at you — empty strings, missing substrings, weird casing, doubled separators, unicode-ish edge cases. Programming bugs almost always live in the edge cases , not the happy path. So we deliberately hunt them down.
Come back to the parent topic anytime a method's basic behaviour feels shaky.
Before solving anything, let's map out every category of input a string method can face. A string is a row of characters indexed 0, 1, 2, …. The "surprises" come from where things sit and whether they exist at all.
Cell
Scenario class
The trap it hides
A
Normal / happy-path text
none — baseline
B
Substring present at index 0
find returns 0, which is falsy
C
Substring absent
find returns -1, not an error
D
Empty string "" or empty pieces
split can produce '' entries
E
Doubled / edge separators (",,", leading ,)
split(sep) keeps empty pieces
F
Whitespace runs vs a single space
split() ≠ split(' ')
G
No letters (digits/symbols only)
upper/lower change nothing
H
Round-trip split→join
must give the original back
I
Real-world word problem
chaining under pressure
J
Exam twist — format percent + limited replace
presentation spec + count arg
The eight examples below hit all ten cells . Each is labelled with the cell(s) it covers.
Worked example 1 — Baseline clean-up (cells A, F, G)
raw = " Hello World 42 "
step = raw.strip() # ?
out = step.lower() # ?
Forecast: what is out? Does the 42 change?
raw.strip() → "Hello World 42".
Why this step? strip() removes leading and trailing whitespace only — the space between "Hello" and "World" is interior, so it survives. This is cell F in miniature: interior spaces are never touched by strip.
"Hello World 42".lower() → "hello world 42".
Why this step? lower() cases letters only . The digits 4 and 2 and the interior space are non-letters, so they pass through unchanged — that is cell G .
Verify: count characters — input had 2+2 padding spaces = 4 removed by strip. len("Hello World 42") = 14. Final string is all-lowercase letters + one space + two digits. ✔️
Worked example 2 — Match at index 0 (the falsy trap) (cells A, B)
word = "python"
pos = word.find( "py" )
Forecast: what is pos? And is if word.find("py"): safe?
word.find("py") slides a window of length 2 across "python". At i = 0, word[0:2] == "py" — match! Returns 0.
Why this step? find returns the index of the first match, and the very first position (index 0) is a match. This is cell B .
Now if word.find("py"): evaluates if 0:. In Python 0 is falsy (see Boolean truthiness ), so the branch is skipped — even though "py" was found!
Why this step? It exposes the classic bug: a real match at the start looks like "not found".
Verify: word.find("py") == 0 is True, but bool(0) is False. The correct test is word.find("py") != -1, or better "py" in word. ✔️
Worked example 3 — Absent substring, no crash (cell C)
s = "the cat sat"
a = s.find( "dog" )
b = ( "dog" in s)
Forecast: does find raise an error when "dog" is missing?
s.find("dog") slides the length-3 window all the way to the last valid start n - m = 11 - 3 = 8, never matches, and returns the sentinel -1.
Why this step? A sentinel value lets you test membership with a plain if a != -1: instead of wrapping calls in exception handling. Cell C .
"dog" in s returns the boolean False.
Why this step? The in operator is the clean way to ask "is it present?" — no -1 bookkeeping.
Verify: s.find("dog") == -1 and ("dog" in s) == False. No exception is raised. ✔️
Worked example 4 — Doubled and edge separators (cells D, E)
csv = ",apple,,banana,"
parts = csv.split( "," )
Forecast: how many pieces come out, and are any of them empty?
split(",") cuts at every comma, keeping whatever sits between two cuts — even if it's nothing.
Why this step? With a specified separator, Python does not collapse runs. Each comma is a hard boundary.
Walk the string: [""], [apple], [""], [banana], [""]. Leading comma ⇒ empty first piece (cell E ); the ,, ⇒ an empty middle piece (cell D ); trailing comma ⇒ empty last piece.
Result: ['', 'apple', '', 'banana', ''] — 5 pieces.
Why this step? Rule of thumb: split(sep) on a string containing k copies of sep always returns k + 1 pieces. Here k = 4 commas ⇒ 5 pieces.
Verify: ",apple,,banana,".split(",") == ['', 'apple', '', 'banana', ''] and its length is 5. ✔️
split() vs split(' ') on whitespace runs (cell F)
text = " a b "
tokens_bare = text.split() # ?
tokens_space = text.split( ' ' ) # ?
Forecast: which one gives a clean ['a','b']?
text.split() (no argument) splits on any run of whitespace and drops empty pieces at the edges. Result: ['a', 'b'].
Why this step? Bare split() is built for human text where you don't care how many spaces there are.
text.split(' ') splits on each single space literally. The leading " " (two spaces) makes two cuts → empty pieces; the " " run makes three cuts → empties between a and b; trailing " " again. Result: ['', '', 'a', '', '', 'b', '', ''].
Why this step? A structural separator keeps every gap, so runs create empties. Cell F .
Verify: " a b ".split() == ['a', 'b'] (length 2) while " a b ".split(' ') has length 8. ✔️
Worked example 6 — Round trip: split then join returns the original (cell H)
original = "a-b-c"
pieces = original.split( "-" ) # ['a', 'b', 'c']
back = "-" .join(pieces) # ?
Forecast: is back exactly equal to original?
original.split("-") → ['a', 'b', 'c'] (3 pieces, since there are 2 dashes → 2+1 pieces).
Why this step? Splitting on the same character we'll re-join with is the setup for the inverse law.
"-".join(['a','b','c']) writes a - between consecutive pieces — that's 3 - 1 = 2 separators — giving "a-b-c".
Why this step? join inserts n - 1 separators for n pieces, exactly matching the 2 dashes we removed. So c.join(s.split(c)) == s. Cell H .
Verify: "-".join("a-b-c".split("-")) == "a-b-c" and the piece count is 3. ✔️
See Lists — split produces a list, join consumes one.
Worked example 7 — Real-world: normalise an email + limited replace (cells I, A)
raw = " Bob.Smith@Example.COM \n "
email = raw.strip().lower()
masked = email.replace( "." , "_" , 1 ) # only the FIRST dot
Forecast: what is email, and does masked change both dots?
raw.strip() → "Bob.Smith@Example.COM".
Why this step? Kill the leading spaces and trailing \n so garbage doesn't leak into the address (cell A happy-path clean-up, from User input and validation ).
.lower() → "bob.smith@example.com".
Why this step? Emails are case-insensitive; normalising lets == comparisons behave. Chaining works because strip() returns a string we can immediately call .lower() on.
email.replace(".", "_", 1) → "bob_smith@example.com".
Why this step? The third argument to replace caps how many swaps happen. With 1, only the first dot (in bob.smith) becomes _; the dot in .com is left alone. Cell I twist.
Verify: email == "bob.smith@example.com" and email.replace(".", "_", 1) == "bob_smith@example.com". ✔️
Worked example 8 — Exam twist: percent formatting (cell J)
name, score = "Ada" , 0.8736
line = " {} scored { :.1% } " .format(name, score)
Forecast: what number appears — 0.8, 87, or 87.4%?
The first {} takes name → "Ada".
Why this step? Empty braces are filled by positional arguments in order.
{:.1%} applied to 0.8736: the % spec multiplies by 100 and appends %; the .1 keeps 1 decimal place with rounding. 0.8736 × 100 = 87.36, rounded to 1 dp → 87.4, then % → "87.4%".
Why this step? The format spec separates the value (0.8736) from its presentation (87.4%) — you store proportions, display percentages. Cell J . (Modern code often uses f-strings : f"{name} scored {score:.1%}".)
Verify: "{} scored {:.1%}".format("Ada", 0.8736) == "Ada scored 87.4%". ✔️
Recall Which cell breaks
if s.find(x):?
Cell B — a match at index 0 returns 0, which is falsy. ::: Use != -1 or the in operator.
Recall How many pieces from
"a,,b".split(",")?
Two commas → 2 + 1 = 3 pieces: ['a', '', 'b']. ::: The empty middle piece is cell D.
Recall Why does
" a b ".split() give length 2 but .split(' ') give more?
Bare split() collapses whitespace runs and drops edge empties; split(' ') treats every single space as a hard cut. ::: Cell F.
Mnemonic Reading the matrix
"Zero, None, Edge, Runs" — the four homes of string bugs: index Zero (falsy find), None -present (−1), Edge separators (leading/trailing), and whitespace Runs .
Parent topic
Strings — indexing and slicing (find slides an s[i:i+m] window)
Lists (split → list, join ← list)
Immutability in Python (every result is a fresh string — reassign!)
f-strings (the modern face of .format)
Boolean truthiness (why 0 and -1 trap you)
User input and validation