7.1 String Basics and Immutability
A Python string is an immutable sequence of Unicode characters. You can create one with single quotes, double quotes, or triple quotes — triple quotes are the only kind that can span multiple lines.
Because strings are immutable, any method that appears to "modify" one — s.upper(), s.replace(...) — actually returns a brand-new string object rather than changing the original.
The original string is left completely untouched, so if you want to keep the result, you must explicitly reassign it back to a variable.
# String Immutability in Action
msg = "hello world"
msg.upper() # Returns "HELLO WORLD", but msg remains unchanged!
print(msg) # Output: "hello world"
msg = msg.upper() # Reassigns variable label to the new string object
print(msg) # Output: "HELLO WORLD"
Because strings are immutable, repeated concatenation with += inside a loop creates a new string object and copies all previous characters on every iteration, leading to terrible O(n²) time complexity. The professional Python idiom is appending strings to a list and combining them once with "".join(list_of_strings), which runs in linear O(n) time.
Two-Pointer Palindrome Verification in Python
While reversing a string with s == s[::-1] is concise, it allocates a full duplicate copy of the string in memory ($O(n)$ space). The classic DSA two-pointer technique uses O(1) extra space:
def is_palindrome(s: str) -> bool:
i, j = 0, len(s) - 1
while i < j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
# is_palindrome("madam") -> True
7.2 Indexing and Slicing
Individual characters are accessed with zero-based indexing (s[0] is the first character), and negative indices count backward from the end (s[-1] is the last character).
Slicing, written s[start:stop:step], extracts a sub-string: the start index is included, but the stop index is always excluded — a rule that makes it easy to grab prefixes, suffixes, or even a reversed copy of the whole string with s[::-1].
s = "PYTHON"
print(s[1:4]) # "YTH" (starts at index 1, stops before index 4)
print(s[:3]) # "PYT" (omitting start defaults to 0)
print(s[3:]) # "HON" (omitting stop defaults to sequence end)
print(s[::2]) # "PTO" (step=2, every second character)
print(s[::-1]) # "NOHTYP" (reverses the string!)
7.3 Common String Methods
Strings ship with a large set of built-in methods for everyday text processing. Every single one of them returns a new string (or a new list) rather than modifying the original in place — consistent with strings being immutable.
Built-in String Methods Reference Table
| Method | Purpose | Example |
|---|---|---|
len(s) |
Number of characters in the string | len("Python") → 6 |
s.strip() |
Removes leading and trailing whitespace | " hi ".strip() → "hi" |
s.split(sep) |
Splits the string into a list of substrings | "a,b,c".split(",") → ['a', 'b', 'c'] |
s.join(list) |
Joins a list of strings together using s as separator | "-".join(['x', 'y']) → "x-y" |
s.lower() / s.upper() |
Case conversion | "Go".lower() → "go" |
s.replace(old, new) |
Replaces occurrences of substring | "cat".replace("c", "b") → "bat" |
s.startswith(prefix) |
Checks if string begins with prefix | "ReadyToGo".startswith("Ready") → True |
s.find(sub) |
Returns index of first match, or -1 if not found | "banana".find("na") → 2 |