Working with Strings in Python
Text in Python: string literals and escapes, indexing and slicing, immutability, the most useful string methods, and splitting and joining text.
Writing strings
A string (str) is an immutable sequence of characters. You write one between single or double quotes; both are identical, which lets you include the other kind of quote without fuss: "It's here" or 'He said "hi"'.
Inside a string, a backslash starts an escape sequence: \n is a newline, \t a tab, \\ a literal backslash and \" a quote. Triple quotes ("""...""") allow text that spans several lines. A raw string, r"...", treats backslashes as ordinary characters, which is handy for Windows paths and regular expressions.
len(text) returns the number of characters. The empty string "" has length 0.
1quote = 'She said "ready" and left.'2print(quote)3print("Line one\nLine two")4print("Name:\tAda")5path = r"C:\new\data"6print(path, len(path))7poem = """Roses are red,8code is in Python."""9print(poem)10print(len(""), len("hello"))
She said "ready" and left. Line one Line two Name: Ada C:\new\data 11 Roses are red, code is in Python. 0 5
Indexing: one character at a time
Each character has a position, its index, starting at 0. Negative indexes count from the end: -1 is the last character, -2 the one before it. Indexing past either end raises IndexError.
Zero-based counting feels odd at first, but it makes the arithmetic clean: the last valid index is always len(text) - 1, and text[-1] is a convenient way to say the same thing.
character P Y T H O N index 0 1 2 3 4 5 negative -6 -5 -4 -3 -2 -1
1word = "PYTHON"2print(word[0], word[1], word[5])3print(word[-1], word[-2])4print(word[len(word) - 1])
P Y N N O N
Slicing: taking a piece
A slice text[start:stop] returns a new string from index start up to, but not including, stop. Leaving out start means "from the beginning" and leaving out stop means "to the end". The number of characters in text[a:b] is simply b - a.
An optional third value is the step: text[::2] takes every second character, and a negative step walks backwards, so text[::-1] is the reversed string.
Unlike indexing, slicing never raises an error for out-of-range positions; it just returns whatever part exists, possibly an empty string.
`text[:n]` and `text[n:]` always split a string into two parts that add back up to `text`, whatever the value of `n`.
1text = "programming"2print(text[0:7])3print(text[3:])4print(text[:3])5print(text[-4:])6print(text[::2])7print(text[::-1])8print(repr(text[20:30]))
program gramming pro ming pormig gnimmargorp ''
Strings cannot be changed in place
Strings are immutable. An assignment such as word[0] = "J" raises TypeError: 'str' object does not support item assignment. Every operation that seems to modify a string, upper(), replace(), slicing, +, actually builds and returns a new string and leaves the original alone.
So the result must be stored, usually by reassigning the name: word = word.upper(). Calling word.upper() on a line by itself computes a new string and then throws it away, a very common beginner bug.
1word = "python"2word.upper()3print(word)4word = word.upper()5print(word)6fixed = "J" + word[1:]7print(fixed)
python PYTHON JYTHON
Joining, repeating and searching
+ concatenates two strings and * repeats a string a whole number of times. Both only work with strings: "Total: " + 5 is a TypeError, so convert numbers with str() or, better, use an f-string.
The in operator checks whether one string occurs inside another and returns a boolean. not in is its negation.
1first, last = "Grace", "Hopper"2full = first + " " + last3print(full)4print("=" * len(full))5print("Hop" in full, "hop" in full, "x" not in full)6print("Items: " + str(3))
Grace Hopper ============ True False True Items: 3
The string methods you will use most
A method is a function attached to a value, called with a dot: text.upper(). Strings come with dozens; this handful covers most everyday work. None of them change the original string.
Common string methods
| Method | What it returns | Example → result |
|---|---|---|
| lower() / upper() | Lower- / upper-case copy | "Hi".upper() → "HI" |
| title() / capitalize() | Each word / only the first letter capitalised | "ada lovelace".title() → "Ada Lovelace" |
| strip() / lstrip() / rstrip() | Copy without surrounding whitespace (or given characters) | " hi ".strip() → "hi" |
| replace(old, new) | Copy with every occurrence replaced | "a-b-c".replace("-", "") → "abc" |
| find(sub) | First index of sub, or -1 | "banana".find("an") → 1 |
| count(sub) | Number of non-overlapping occurrences | "banana".count("a") → 3 |
| startswith(p) / endswith(s) | True or False | "data.csv".endswith(".csv") → True |
| isdigit() / isalpha() / isalnum() | True if every character qualifies (False for "") | "2026".isdigit() → True |
1raw = " Ada LOVELACE "2name = raw.strip().title()3print(f"[{name}]")4email = "Ada@Example.COM"5print(email.lower(), email.find("@"), email.count("a"))6print("report_2026.csv".endswith(".csv"), "2026".isdigit(), "20 26".isdigit())7print("2026-10-20".replace("-", "/"))
[Ada Lovelace] ada@example.com 3 2 True True False 2026/10/20
Splitting text and joining pieces
text.split(sep) cuts a string at every sep and returns a list of the pieces. With no argument, split() splits on any run of whitespace and ignores leading and trailing spaces, which is usually what you want for words.
The reverse operation is sep.join(pieces): it glues a list of strings together with sep between them. Note that join is a method of the separator, and every piece must already be a string.
`"a b".split(" ")` returns `['a', '', 'b']`: splitting on a single space keeps empty pieces between repeated spaces. Use `split()` with no argument for words.
1sentence = " the quick brown fox "2words = sentence.split()3print(words)4print(len(words), "words")5print("-".join(words))6row = "2026,10,20"7print(row.split(","))8print("".join(["P", "y", "t", "h", "o", "n"]))9print(", ".join(str(n) for n in [1, 2, 3]))
['the', 'quick', 'brown', 'fox'] 4 words the-quick-brown-fox ['2026', '10', '20'] Python 1, 2, 3
Walking through the characters
A string is a sequence, so a for loop visits its characters one by one. (Loops have their own lesson; this is a first look.) Combining a loop with the methods above is how most text processing works: counting, filtering, building a new string.
Every character also has a numeric code point: ord("A") is 65 and chr(65) is "A". Python strings are Unicode, so they hold accented letters and scripts from every language, not only English letters.
1text = "Order 66 shipped on 2026-10-20"2digits = ""3letters = 04for ch in text:5 if ch.isdigit():6 digits += ch7 elif ch.isalpha():8 letters += 19print(digits)10print(letters, "letters")11print(ord("A"), chr(66), ord("a") - ord("A"))
6620261020 14 letters 65 B 32
Check your understanding
What does `print("python"[1:4])` print?
Practice
Start with initials from a name (split, indexing, join), then answer six questions about a piece of text with slices, methods and a counting loop.
Name Initials
Reveal Solution & AnalysisHide Solution
String Manipulation Mastery
Reveal Solution & AnalysisHide Solution
Run-Length Encoding
Reveal Solution & AnalysisHide Solution
Quick Syntax & Reference Sheet
| Concept / Rule | Syntax / Usage |
|---|---|
| Length | len(text) |
| First / last character | text[0], text[-1] |
| Slice (stop excluded) | text[2:5], text[:3], text[3:] |
| Reverse | text[::-1] |
| Clean user input | text.strip().lower() |
| Words of a sentence | sentence.split() |
| Join with a separator | ", ".join(items) |
| Substring test | "@" in email |
Common Pitfalls & Developer Mistakes
Calling a method without storing the result
Why it happens: Strings are immutable, so text.strip() on its own changes nothing.
Off-by-one slices
Why it happens: The stop index is excluded: text[0:3] has three characters, indexes 0, 1 and 2.
Using split(" ") to get words
Why it happens: Repeated spaces produce empty strings in the result.
Joining non-strings
Why it happens: ", ".join([1, 2]) raises TypeError because join needs strings.