Strings are the most frequently used data type in web development — every URL, form input, API response field, and database column value passes through string operations at some point. Python strings are immutable Unicode sequences, which means you cannot change a character in place — string operations always return a new string. Python’s string handling is powerful and concise, and several features — particularly f-strings and slicing — are more expressive than their JavaScript equivalents. Mastering strings now will make working with FastAPI request bodies, PostgreSQL query results, and React API responses much smoother.
Creating Strings
# Single quotes or double quotes — equivalent
name1 = 'Alice'
name2 = "Alice"
# Use the other quote type to embed quotes without escaping
message1 = "It's a great day" # single quote inside double-quoted string
message2 = 'He said "hello"' # double quote inside single-quoted string
# Escape sequences
tab_example = "Name:\tAlice" # \t = tab
newline_ex = "Line1\nLine2" # \n = newline
escaped_quote = "She said \"hi\""
# Raw strings — backslashes are literal (useful for regex and Windows paths)
raw = r"C:\Users\alice\documents" # no escape processing
print(raw) # C:\Users\alice\documents
# Multiline strings with triple quotes
description = """This is a
multiline string that spans
several lines."""
bio = '''Another way
to write multiline
strings.'''
name[0] = "B" raises a TypeError. To modify a string, you create a new one: name = "B" + name[1:]. This immutability is by design — it makes strings hashable (usable as dictionary keys) and safe to share across threads. FastAPI’s Pydantic models rely on this behaviour when validating string fields.f"Hello, {name}!" vs "Hello, %s!" % name vs "Hello, {}!".format(name). F-strings were introduced in Python 3.6 and are the community standard for all new code."Alice" == "alice" is False. When comparing user input or database values, normalise case first with .lower() or .upper(). This is a frequent source of bugs in authentication systems — a user who registers as “Alice@email.com” cannot log in as “alice@email.com” unless you normalise the email before comparison.F-Strings — String Interpolation
name = "Alice"
age = 30
city = "Sydney"
# Basic f-string
greeting = f"Hello, {name}!" # "Hello, Alice!"
# Expressions inside f-strings
summary = f"{name} is {age} years old." # "Alice is 30 years old."
calc = f"Next year: {age + 1}" # "Next year: 31"
upper = f"Uppercase: {name.upper()}" # "Uppercase: ALICE"
# Format specifiers
price = 19.99
display = f"Price: ${price:.2f}" # "Price: $19.99"
pct = f"Progress: {0.756:.1%}" # "Progress: 75.6%"
padded = f"{'left':<10}|" # "left |" (left-align, 10 wide)
# Multiline f-string
profile = (
f"Name: {name}\n"
f"Age: {age}\n"
f"City: {city}"
)
print(profile)
# Name: Alice
# Age: 30
# City: Sydney
Essential String Methods
text = " Hello, World! "
# Case methods
text.upper() # " HELLO, WORLD! "
text.lower() # " hello, world! "
text.title() # " Hello, World! "
text.capitalize() # " hello, world! " (only first char of string)
# Whitespace
text.strip() # "Hello, World!" (both ends)
text.lstrip() # "Hello, World! " (left only)
text.rstrip() # " Hello, World!" (right only)
# Search
"Hello, World!".find("World") # 7 (index of first match, -1 if not found)
"Hello, World!".index("World") # 7 (raises ValueError if not found)
"Hello, World!".count("l") # 3
"Hello, World!".startswith("Hello") # True
"Hello, World!".endswith("!") # True
"World" in "Hello, World!" # True (membership operator)
# Replace and split
"Hello, World!".replace("World", "Python") # "Hello, Python!"
"a,b,c,d".split(",") # ["a", "b", "c", "d"]
" spaced out ".split() # ["spaced", "out"] (splits on whitespace)
",".join(["a", "b", "c"]) # "a,b,c"
# Check content
"42".isdigit() # True
"abc".isalpha() # True
"abc123".isalnum() # True
" ".isspace() # True
String Slicing
text = "Hello, World!"
# 0123456789...
# text[start:stop:step] — stop is exclusive
text[0] # "H" — first character
text[-1] # "!" — last character
text[0:5] # "Hello" — chars 0,1,2,3,4
text[7:] # "World!" — from index 7 to end
text[:5] # "Hello" — from start to index 4
text[::2] # "Hlo ol!" — every 2nd character
text[::-1] # "!dlroW ,olleH" — reversed string
# Practical uses
email = "alice@example.com"
domain = email.split("@")[1] # "example.com"
ext = domain.split(".")[1] # "com"
slug = "hello-world-post"
words = slug.split("-") # ["hello", "world", "post"]
title = " ".join(w.capitalize() for w in words) # "Hello World Post"
Common Mistakes
Mistake 1 — Trying to modify a string in place
❌ Wrong — strings are immutable:
name = "alice"
name[0] = "A" # TypeError: 'str' object does not support item assignment
✅ Correct — create a new string:
name = "alice"
name = name.capitalize() # "Alice" — new string assigned to name ✓
Mistake 2 — Forgetting that find() returns -1 (not None) when not found
❌ Wrong — treating find() like JavaScript's indexOf:
result = "hello".find("xyz")
if result: # -1 is truthy! This block executes even when not found
print("Found") # prints "Found" — wrong!
✅ Correct — compare to -1 explicitly:
result = "hello".find("xyz")
if result != -1: # ✓ explicit check
print("Found")
Mistake 3 — Case-sensitive comparison without normalising
❌ Wrong — emails compared with different cases:
stored_email = "Alice@Example.com"
login_email = "alice@example.com"
if stored_email == login_email: # False — bug!
✅ Correct — normalise before comparing:
if stored_email.lower() == login_email.lower(): # True ✓
Quick Reference
| Task | Code |
|---|---|
| Interpolate variable | f"Hello, {name}!" |
| Format 2 decimal places | f"{value:.2f}" |
| Remove whitespace | text.strip() |
| Lowercase | text.lower() |
| Split on delimiter | "a,b".split(",") → ["a","b"] |
| Join list to string | ",".join(["a","b"]) → "a,b" |
| Check if substring exists | "x" in text |
| Replace substring | text.replace("old", "new") |
| Reverse a string | text[::-1] |
| Get string length | len(text) |