Working with Strings
1 · The lesson
readStrings are the type you'll touch in every program you write — filenames, user input, API responses, log lines, error messages. Python's string handling is one of the cleanest in any language, and the dozen methods in this lesson cover roughly 90% of real-world text work.
1. Three Ways to Quote
single = 'Alice' double = "Alice" triple = """Alice spans multiple lines."""
Single and double quotes are interchangeable — pick whichever avoids escaping. Use double when the string contains an apostrophe, single when it contains a quotation mark:
print("It's fine.") # no need to escape the apostrophe print('She said "hi".') # no need to escape the quotes
Triple quotes ("""...""" or '''...''') preserve line breaks and are how you write multi-line strings and docstrings.
message = """Dear Alice, Welcome aboard. — The Team""" print(message)
2. Strings Are Immutable
You can't change a string in place. Every "modification" returns a brand new string.
name = "surya" name.upper() # returns "SURYA" — but we threw it away print(name) # "surya" — unchanged name = name.upper() # reassignment is how you "update" print(name) # "SURYA"
Try to mutate one and Python refuses:
s = "hello" s[0] = "H" # TypeError: 'str' object does not support item assignment
This is by design — strings are cheap to share around your program precisely because nobody can modify them behind your back.
3. Indexing and Slicing
Each character has a position, starting at 0. Negative indices count from the end.
s = "Python" print(s[0]) # 'P' print(s[3]) # 'h' print(s[-1]) # 'n' — last character print(s[-2]) # 'o' — second-to-last
Slicing pulls out a range with s[start:stop:step]. stop is exclusive.
s = "Python rocks" print(s[0:6]) # 'Python' — characters 0,1,2,3,4,5 print(s[7:]) # 'rocks' — from 7 to end print(s[:6]) # 'Python' — from start to 6 print(s[-5:]) # 'rocks' — last 5 chars # The step argument print(s[::2]) # 'Pto ok' — every 2nd character print(s[::-1]) # 'skcor nohtyP' — reversed (idiomatic)
Slices never raise an IndexError for out-of-range bounds — they silently clamp. Bare indices do raise.
print(s[100:200]) # '' — empty slice, no error print(s[100]) # IndexError: string index out of range
setup added so this can run · defines s
s = ["alpha", "beta", "gamma"]
4. Joining and Repeating
greeting = "Hi, " + "Alice" # 'Hi, Alice' line = "-" * 30 # '------------------------------' shout = "ha" * 5 # 'hahahahaha'
+ only works between two strings. Mixing types throws TypeError:
age = 30 print("I am " + age + " years old.") # TypeError: can only concatenate str (not "int") to str print("I am " + str(age) + " years old.") # OK — but f-strings (next lesson) do this better
5. The Methods You'll Use Daily
Each of these returns a new string (or a list, or a bool) — the original is untouched.
text = " Hello, World! " print(text.upper()) # ' HELLO, WORLD! ' print(text.lower()) # ' hello, world! ' print(text.strip()) # 'Hello, World!' — trims whitespace both ends print(text.replace("World", "Alice")) # ' Hello, Alice! '
Splitting and joining — the two methods you'll reach for most often:
csv = "apple,banana,cherry" parts = csv.split(",") # ['apple', 'banana', 'cherry'] print(parts[1]) # 'banana' words = "the quick brown fox".split() # no arg → split on any whitespace print(words) # ['the', 'quick', 'brown', 'fox'] # join() is the inverse — glue an iterable of strings with a separator print("-".join(["2026", "05", "14"])) # '2026-05-14' print(", ".join(words)) # 'the, quick, brown, fox'
Searching and checking:
email = "alice@example.com" print(email.startswith("surya")) # True print(email.endswith(".com")) # True print(email.find("@")) # 5 — index of first match, -1 if missing print(email.count("e")) # 2 — non-overlapping occurrences print("@" in email) # True — `in` is the cleanest substring check print("xyz" in email) # False
find() returns -1 when the substring is absent. There's also .index() which raises ValueError instead — use whichever fits your control flow.
6. Escape Sequences
Inside a normal string, \ introduces a special character.
print("Line one\nLine two") # \n is a newline print("Name:\tAlice") # \t is a tab print("She said \"go\".") # escape the quote that matches the outer quote print("C:\\Users\\Alice") # escape the backslash itself
The full set you'll see in the wild: \n newline, \t tab, \r carriage return, \\ backslash, \" and \' quotes, \0 null byte.
7. Raw Strings
Prefix a string with r and backslashes lose their special meaning. This matters most for Windows paths and regular expressions.
path = r"C:\Users\Alice\Documents" print(path) # C:\Users\Alice\Documents # Without the r, you'd need to double every backslash: path = "C:\\Users\\Alice\\Documents"
In regex patterns (covered later), raw strings keep \d, \w, \b readable:
import re pattern = r"\d{4}-\d{2}-\d{2}" # matches dates like 2026-05-14 print(re.findall(pattern, "Today is 2026-05-14.")) # ['2026-05-14']
8. Length
print(len("Python")) # 6 print(len("")) # 0 — empty string is valid print(len(" spaces ")) # 10 — whitespace counts
len() is a built-in function, not a method — len(s), never s.len().
9. F-Strings Are Coming
You've seen f"Hi, {name}" in earlier lessons. The next lesson — F-Strings — covers them in depth: format specifiers, alignment, debug syntax. For now, just know that f-strings are how you build dynamic strings in modern Python. Skip + concatenation when you can.
Common Mistakes
1. Trying to mutate a string.
s = "hello" s[0] = "H" # TypeError — strings are immutable s = "H" + s[1:] # correct: build a new string
2. Confusing .split() with no args vs .split(",").
"a b c".split() # ['a', 'b', 'c'] — any whitespace, collapses runs "a b c".split(" ") # ['a', 'b', '', 'c'] — exactly one space, keeps empties
Use bare
.split() for human-typed text. Use .split(",") (or another explicit delimiter) for structured data like CSV.
3. Passing non-strings to .join().
",".join([1, 2, 3]) # TypeError: sequence item 0: expected str instance, int found ",".join(str(n) for n in [1, 2, 3]) # '1,2,3' — convert first
4. Building long strings with + in a loop.
# Slow — each += allocates a new string result = "" for word in words: result += word + " " # Fast — build a list, join once at the end result = " ".join(words)
setup added so this can run · defines words
words = ["alpha", "beta", "gamma"]
On a few hundred items the difference is invisible; on a million it's the difference between snappy and minutes.
🎯 Your Turn — Build a Slugifier
Write a function slugify(text) that turns a human title into a URL-safe slug:
"Hello, World! 2026" → "hello-world-2026" " Python Rocks " → "python-rocks" "It's --- awesome!" → "it-s-awesome"
Rules:
1. Lowercase everything.
2. Replace any character that isn't a letter or digit with -.
3. Collapse consecutive - into a single -.
4. Strip leading and trailing -.
Skeleton:
def slugify(text): # TODO 1: lowercase # TODO 2: replace non-alphanumeric chars with '-' # TODO 3: collapse runs of '-' # TODO 4: strip leading/trailing '-' return ... print(slugify("Hello, World! 2026")) # expect: 'hello-world-2026' print(slugify(" Python Rocks ")) # expect: 'python-rocks'
Hint 1 — Checking if a character is alphanumeric
Strings have an.isalnum() method that returns True for letters and digits. You can build the result one character at a time:
cleaned = "".join(c if c.isalnum() else "-" for c in text.lower())That's a generator expression inside
"".join() — a very Pythonic pattern.
Hint 2 — Collapsing repeated dashes
After step 2 you'll have strings like"hello--world----2026". The dumb-but-effective trick: keep replacing "--" with "-" in a while loop until no more changes happen. Then call .strip("-") to remove leading/trailing dashes.
Show full solution
def slugify(text): # Lowercase, then replace non-alphanumeric with '-' cleaned = "".join(c if c.isalnum() else "-" for c in text.lower()) # Collapse consecutive dashes while "--" in cleaned: cleaned = cleaned.replace("--", "-") # Trim dashes from both ends return cleaned.strip("-") print(slugify("Hello, World! 2026")) # 'hello-world-2026' print(slugify(" Python Rocks ")) # 'python-rocks' print(slugify("It's --- awesome!")) # 'it-s-awesome' print(slugify("---")) # ''
Three lines of real logic. The c if c.isalnum() else "-" ternary inside the generator expression is the heart of it — you'll see that pattern in real codebases for any "transform each character" job.
A production version would use the re module: re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-") does the whole thing in one line. Same idea, denser syntax.
What You Learned
- Three quote styles — single, double, triple — pick to avoid escaping.
- Strings are immutable. Every method returns a new string.
- Slicing
s[start:stop:step]with negative indices and step.s[::-1]reverses. - The 10 daily methods:
.upper(),.lower(),.strip(),.split(),.join(),.replace(),.startswith(),.endswith(),.find(),.count(). inis the cleanest substring test.r"..."for raw strings — paths and regex.len(s)for length. Nevers.len().
Next: F-Strings — the modern way to build dynamic strings, with format specifiers that make numbers and alignment trivial.
Practice this
on practicepython.inShort exercises that run in your browser and tell you what your code actually did, not just whether a test passed.