Working with Strings
1 · The lesson
readYou will use strings in almost every program you write: for filenames, user input, API responses, log lines and error messages. Python makes text easy to work with, and the dozen methods in this lesson cover most everyday text tasks.
1. Three Ways to Quote
single = 'Alice' double = "Alice" triple = """Alice spans multiple lines."""
Single and double quotes do the same thing. Pick the one that lets you avoid escaping (writing \ before a quote character). 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 the result is not saved anywhere) print(name) # "surya" (unchanged) name = name.upper() # to "update" a string, assign the new one to the name 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 on purpose. Because no code can change a string, many parts of a program can safely use the same string object without copying it.
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' (the last character) print(s[-2]) # 'o' (the second-to-last character)
Slicing pulls out a range with s[start:stop:step]. stop is exclusive.
s = "Python rocks" print(s[0:6]) # 'Python' (characters 0 to 5) print(s[7:]) # 'rocks' (from index 7 to the end) print(s[:6]) # 'Python' (from the start, stopping before 6) print(s[-5:]) # 'rocks' (the last 5 characters) # The step argument print(s[::2]) # 'Pto ok' (every 2nd character) print(s[::-1]) # 'skcor nohtyP' (reversed, the usual Python way)
Slices never raise an IndexError when the bounds are out of range. Python quietly limits them to the length of the string. A single index that is out of range does raise an IndexError.
s = "Python" print(s[100:200]) # '' (an empty slice, and no error) print(s[100]) # IndexError: string index out of range
4. Joining and Repeating
greeting = "Hi, " + "Alice" # 'Hi, Alice' line = "-" * 30 # '------------------------------' shout = "ha" * 5 # 'hahahahaha'
+ only works between two strings. If you mix types, Python raises a 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.") # works, but f-strings (next lesson) do this better
5. The Methods You'll Use Every Day
Each of these returns a new string (or a list, or a bool). The original string does not change.
text = " Hello, World! " print(text.upper()) # ' HELLO, WORLD! ' print(text.lower()) # ' hello, world! ' print(text.strip()) # 'Hello, World!' (removes whitespace at both ends) print(text.replace("World", "Alice")) # ' Hello, Alice! '
Splitting and joining, the two methods you will use most often:
csv = "apple,banana,cherry" parts = csv.split(",") # ['apple', 'banana', 'cherry'] print(parts[1]) # 'banana' words = "the quick brown fox".split() # with no argument, split on any whitespace print(words) # ['the', 'quick', 'brown', 'fox'] # join() does the opposite. It joins a list of strings, with a separator between them 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("alice")) # True print(email.endswith(".com")) # True print(email.find("@")) # 5 (index of the first match, or -1 if there is none) print(email.count("e")) # 3 (counts matches that do not overlap) print("@" in email) # True (`in` is the cleanest way to check for a substring) print("xyz" in email) # False
find() returns -1 when the substring is absent. There is also .index(), which raises a ValueError instead. Use .find() if you want to check the result with if, and .index() if a missing substring should be an error.
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 escape sequences you will see most often: \n newline, \t tab, \r carriage return, \\ backslash, \" and \' quotes, \0 null byte.
7. Raw Strings
If you put r before a string, 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 (an empty string is still a valid string) print(len(" spaces ")) # 10 (spaces count as characters)
len() is a built-in function, not a method. Write len(s), not 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 and debug syntax. For now, just know that f-strings are how you build dynamic strings in modern Python. Use them instead of + 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'] (several spaces in a row count as one) "a b c".split(" ") # ['a', 'b', '', 'c'] (splits at every space, keeps empty strings)
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 each number to a string first)
4. Building long strings with + in a loop.
# Slow: each += creates a new string result = "" for word in words: result += word + " " # Fast: join the whole list once, at the end result = " ".join(words)
setup added so this can run · re-runs 1 earlier block on this page first
words = ["alpha", "beta", "gamma"] # Earlier blocks on this page that this one builds on, re-run quietly first. _pm_earlier = [ 'csv = "apple,banana,cherry"\nparts = csv.split(",") # [\'apple\', \'banana\', \'cherry\']\nprint(parts[1]) # \'banana\'\n\nwords = "the quick brown fox".split() # with no argument, split on any whitespace\nprint(words) # [\'the\', \'quick\', \'brown\', \'fox\']\n\n# join() does the opposite. It joins a list of strings, with a separator between them\nprint("-".join(["2026", "05", "14"])) # \'2026-05-14\'\nprint(", ".join(words)) # \'the, quick, brown, fox\'', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
With a few hundred items, you won't notice the difference. With a very large number of items, the
join() version can be much faster.
🎯 Your Turn: Build a Slugifier
Write a short program that turns a title into a slug, a short version that is safe to use in a URL:
"Hello, World! 2026" → "hello-world-2026" " Python Rocks " → "python-rocks" "It's --- awesome!" → "it-s-awesome"
Rules:
1. Lowercase everything.
2. Replace the punctuation characters , ! ' and - with spaces.
3. Split the text into words, and join the words with -.
Step 3 also removes extra spaces, because .split() with no argument ignores spaces at both ends and treats several spaces in a row as one.
Skeleton:
title = "Hello, World! 2026" # TODO 1: make the title lowercase # TODO 2: replace "," "!" "'" and "-" with spaces # TODO 3: split into words, then join the words with "-" slug = ... print(slug) # expect: hello-world-2026
When it works, change title to " Python Rocks " (expect python-rocks) and to "It's --- awesome!" (expect it-s-awesome), and run it again.
Hint 1: Calling one method after another
Each string method returns a new string, so you can call the next method on the result:title.lower().replace(",", " ").replace("!", " "). To replace the ' character, put it inside double quotes: .replace("'", " ").
Hint 2: Split, then join
" it s awesome ".split() gives ['it', 's', 'awesome']. Then "-".join(words) puts one - between the words, and none at the ends.
Show full solution
title = "Hello, World! 2026" cleaned = title.lower() cleaned = cleaned.replace(",", " ").replace("!", " ").replace("'", " ").replace("-", " ") words = cleaned.split() slug = "-".join(words) print(words) # ['hello', 'world', '2026'] print(slug) # hello-world-2026
With title = "It's --- awesome!", words is ['it', 's', 'awesome'] and the slug is it-s-awesome.
This version handles only the punctuation characters you list. A title with ? or : needs another .replace() call. A production version would use the re module: re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-") replaces every run of characters that are not a lowercase letter or a digit, in one line. The code is shorter, but harder to read.
What You Learned
- Three quote styles: single, double and triple. Pick the one that avoids 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 methods you will use most:
.upper(),.lower(),.strip(),.split(),.join(),.replace(),.startswith(),.endswith(),.find(),.count(). inis the simplest way to check for a substring.r"..."makes a raw string, useful for Windows paths and regex.len(s)for length. Nevers.len().
Next: F-Strings, the modern way to build strings from values, with format specifiers that make numbers and alignment easy.
Practice this
on practicepython.inShort exercises that run in your browser and tell you what your code did, as well as whether a test passed.
Found this useful? Share it with a friend who’s learning Python.