Strings are immutable sequences of Unicode characters in Python. The str type gives you rich methods for searching, splitting, replacing, and formatting text. This lesson covers everything you need to work with strings in real code, from the basics to modern f-string formatting.
Creating and inspecting strings
Use single or double quotes for a one-line string, and triple quotes for a multi-line string. Triple-quoted strings are also the standard way to write docstrings and SQL or HTML literals.
s = "Hello, World!"
path = 'C:\\Users\\asha'
multiline = """This string
spans multiple
lines."""
print(len(s)) # 13
print(s[0]) # H
print(s[-1]) # !
print(s[7:12]) # World
Common string methods
Python strings ship with dozens of methods. The most useful day-to-day are strip, split, join, replace, startswith, endswith, find, count, upper, lower, and the case-insensitive cousins. Most return a new string because strings are immutable.
raw = " hello, WORLD \n"
clean = raw.strip().lower()
print(clean) # hello, world
csv = "apple,banana,cherry"
fruits = csv.split(",")
print(fruits) # ['apple', 'banana', 'cherry']
joined = " | ".join(fruits)
print(joined) # apple | banana | cherry
print("hello, world".startswith("hello")) # True
print("hello, world".endswith("world")) # True
print("hello, world".count("o")) # 2
print("hello, world".replace("world", "Python")) # hello, Python
f-strings and format specifiers
f-strings are the modern way to interpolate values into strings. Prefix the string with f and embed expressions inside curly braces. Use a colon after the expression for a format specifier, like :.2f for two decimal places.
name = "Asha"
balance = 1234.5
print(f"{name} has a balance of {balance:.2f}") # Asha has a balance of 1234.50
# Width and alignment
for i in range(1, 4):
print(f"{i:3} | {i ** 2:5} | {i ** 3:6}")
# 1 | 1 | 1
# 2 | 4 | 8
# 3 | 9 | 27
# Debug with =
user_id = 42
print(f"{user_id=}") # user_id=42
Searching and validating
Use in to test for a substring. find returns the index or -1. index returns the index or raises. startswith and endswith are useful for parsing file paths and CSV headers.
text = "the quick brown fox"
print("fox" in text) # True
print(text.find("brown")) # 10
print(text.find("purple")) # -1
# Validate format
filename = "report_2026.csv"
if filename.endswith(".csv") and filename.startswith("report_"):
print("valid report file")
Encoding: str versus bytes
str holds Unicode text. bytes holds raw 8-bit values. To send a str over the network or write it to a binary file, encode it. To read bytes back, decode. The default encoding is UTF-8, which handles every language.
text = "नमस्ते Python"
encoded = text.encode("utf-8")
print(encoded) # b'\xe0\xa4\xa8\xe0\xa4\xae\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa5\x87 Python'
decoded = encoded.decode("utf-8")
print(decoded == text) # True
Common pitfalls with string handling
Three mistakes appear over and over. First, mixing str and bytes silently raises TypeError in modern Python, which is the desired behaviour, but Python 2 used to coerce them and old habits still leak into code reviews. Always choose a boundary: text inside the program is str, bytes only on the I/O edge. Second, chaining many string concatenations in a loop is O(n^2) because each + allocates a new string. Use "".join(parts) or io.StringIO when the parts come from a loop. Third, naive case-insensitive comparison via .lower() fails for some Unicode characters (the German sharp s and Turkish dotted/dotless I are famous). For locale-aware work, use casefold(), which applies Unicode case folding rules. For matching patterns, switch to regular expressions with the re.IGNORECASE flag and accept the same caveats.
Comparison with JavaScript template literals and C++ std::string
If you are coming from JavaScript, Python f-strings are the moral equivalent of template literals. Both support expression interpolation, both nest, both allow format specifiers. The differences are minor: JavaScript uses backticks and ${...}; Python uses f and {...}. JavaScript lets you call functions inside the interpolation, while Python's f-strings also accept any expression including function calls (f"{user.name().upper()}"). C++ std::string is a contiguous character container with a different mental model. std::string is mutable in place, supports operator+, and exposes iterators. C++20 std::format gives a Python-like format mini-language, but it is verbose and the compiler errors are notoriously noisy. Python's str is immutable, which forces you to think of every string operation as returning a new value; that immutability is exactly what makes strings hashable and safe to share across threads.
Performance notes for string operations
Three habits keep string-heavy code fast. First, when concatenating many small pieces, build a list and join once: "".join(parts) is faster than "".join(part for part in patterns) and dramatically faster than repeated +=. Second, if you need to test many substrings, regex with re.compile is faster than calling re.search on a literal each time, because the pattern is compiled once and reused. Third, for huge text blobs where you only need a subset, work line by line with a generator: for line in open("huge.txt", encoding="utf-8"): processes the file in constant memory, no matter how big. Combine these with the standard textwrap module for paragraph-level wrapping and the difflib module for change-detection, and most string problems reduce to a few standard idioms.
Summary
Strings are immutable, so every method returns a new string. Use f-strings for formatting. Encode and decode at the network boundary. The next lesson covers regular expressions, the powerful pattern-matching tool for finding, extracting, and replacing text.