Strings — Working with Text in Python

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.'''
Note: Python strings are immutable — once created, the characters cannot be changed. This means 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.
Tip: Always prefer f-strings (formatted string literals) over older formatting methods. F-strings are faster, more readable, and support full Python expressions inside the curly braces. Compare: 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.
Warning: String comparison in Python is case-sensitive: "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)

🧠 Test Yourself

You receive a user email " Alice@EXAMPLE.COM " from a form. Write a single expression to produce a clean, lowercased, whitespace-free email ready to store in the database.