Python Strings — The Basics
Build a real mental model of Python strings: why they are immutable sequences, how indexing and slicing reach into them, how f-strings assemble them, and how the everyday methods each return a new string.
Introduction
A string is a sequence of characters — text held in a single value. Almost everything a program reads or shows is a string: a name, a file path, a line of input, the label on a button. You write one with quotes, and Python treats single quotes and double quotes as two ways to spell exactly the same kind of value.
Why does text deserve its own type, separate from numbers? Because the operations are different in kind: you don't add two names, you *join* them; you don't halve a sentence, you *slice* it. A dedicated string type gives you a shared vocabulary — length, indexing, slicing, searching, splitting — for treating text as a first-class thing rather than a loose pile of characters.
The single idea that makes the rest fall into place is immutability. A Python string can never be changed after it is created. Every operation that looks like it edits a string actually builds and returns a brand-new one. Hold on to that and everything below — indexing, slicing, f-strings, searching, and the transforming methods — is just detail hung on one frame: a string is a fixed sequence you read from and derive new strings from.
single = 'hello'
double = "hello"
triple = """spans
multiple lines""" # triple quotes for multi-line text
Lessons
1. Strings are immutable sequences
This is the foundation. A string is immutable: you cannot reassign one of its characters, and no method changes it in place. upper(), replace(), strip() — each one leaves the original untouched and hands you a *new* string. If you want the result, you have to capture the return value; calling a method and ignoring what it returns changes nothing at all. Trying to assign into a position, as in s[0] = 'H', raises a TypeError rather than mutating the text.
greeting = 'hello'
greeting.upper() # returns 'HELLO' — but we ignored it
print(greeting) # 'hello' — unchanged
greeting = greeting.upper() # capture the new string
print(greeting) # 'HELLO'
Because a string is immutable, you never worry that one part of your program will quietly rewrite text another part is holding — a common source of bugs with mutable values like lists. The trade-off is small: you must *reassign* to keep a transformed result. The word *sequence* matters too, because it means the same reading tools you meet next — indexing and slicing — work on strings exactly as they work on other ordered collections.
2. Indexing: reaching a single character
A string knows its size through len(s), and you reach any character by its index, counting from 0. So s[0] is the first character and s[len(s) - 1] is the last. Python also allows *negative* indices that count from the end, which is far more readable: s[-1] is the last character, s[-2] the one before it. An index that lands past the end raises an IndexError, so indexing is precise and unforgiving — it always names exactly one position.
word = "Python"
print(len(word)) # 6
print(word[0]) # 'P' (first character)
print(word[-1]) # 'n' (last character, via a negative index)
print(word[1]) # 'y'
3. Slicing: copying a run of characters
Where an index takes one character, a slice takes a range. The form is s[start:stop], and like the rest of Python it is half-open — it includes start and excludes stop, so s[1:4] gives you the characters at positions 1, 2 and 3. Omit either end to run to the boundary: s[:3] is the first three characters, s[3:] is everything from position 3 onward. A third number sets a step, so s[::-1] walks backward and reverses the string. Because a string is immutable, a slice can only ever build a new string; the original is never touched.
s = "Python"
print(s[0]) # 'P' one character
print(s[1:4]) # 'yth' positions 1, 2, 3 (stop excluded)
print(s[:2]) # 'Py' from the start
print(s[-2:]) # 'on' the last two characters
4. Building strings: f-strings and concatenation
You can glue strings together with +, but the modern, readable way to build one from values is an f-string — a string prefixed with f where {...} drops a value straight into the text. An f-string keeps the sentence in one piece instead of scattering it across + signs and manual conversions, and it can format numbers inline. Concatenation still has its place for simple joins, but reach for an f-string whenever you are mixing text with values.
name = "Ada"
lang = "Python"
plus = "Hi " + name + ", " + lang + " fan" # concatenation
tmpl = f"Hi {name}, {lang} fan" # f-string
print(plus)
print(tmpl)
Both lines above produce the same text, but the f-string reads as the sentence you actually mean. Notice too that concatenating with + only works between strings — mixing in a number needs str(n) first, whereas an f-string converts values for you inside the braces.
5. Searching inside a string
To ask whether a string contains something, the plainest tool is the in operator, which returns a simple True or False. When you need the position, find() returns the index of the first match or -1 if there is none, and startswith() / endswith() answer the common edge questions directly. These are the everyday tools for validation and matching, and they read almost like English.
url = "xcodx.io/editor"
print("xcodx" in url) # True
print(url.find("editor")) # 9 (-1 if absent)
print(url.startswith("xcodx")) # True
6. The everyday methods: case, whitespace, replace, split, join
The remaining daily operations all follow the same immutable pattern — each method returns a new string (or, for split, a new list). upper() / lower() change case; strip() removes surrounding whitespace, which is invaluable for cleaning user input; replace() swaps one piece of text for another; split() breaks a string into a list of pieces around a separator; and join() is its inverse, gluing a list back into a string. Two of these are worth their own reference pages.
s = " Hello World "
print(s.strip().upper()) # 'HELLO WORLD'
print(s.strip().replace("World", "there")) # 'Hello there'
print("a,b,c".split(",")) # ['a', 'b', 'c']
print("-".join(["a", "b", "c"])) # 'a-b-c'
str.split()— break a string into a list, e.g. a comma-separated line into fields.str.replace()— return a copy with every occurrence of some text swapped for another.strip(),upper(),lower(),startswith()— small, single-purpose transforms you will reach for constantly.
That is the whole model. A string is an immutable sequence of characters; len and zero-based indexing reach a single position; a slice copies a range; an f-string assembles a new string from values; in and find search it; and each transforming method hands back a *new* string. Everything else is one specific method resting on those ideas — you understand strings when you can predict, before running the code, which operations read and which ones build something new.
Practice
Check your model first. Given s = 'hello', what does s.upper() on its own line leave in s? The answer is 'hello' — the method returns 'HELLO', but the string is immutable, so s is unchanged unless you reassign it. And what is s[1:3]? It is 'el': start included, stop excluded.
Now reason about slicing at the edges. Predict the output of the snippet below before you run it — the negative index and the reverse slice are the two ideas most worth internalising. Open it in the Python compiler and confirm your prediction:
name = "ada lovelace"
# Capitalise the first letter without mutating `name`
capitalised = name[0].upper() + name[1:]
print(capitalised) # 'Ada lovelace'
print(name) # 'ada lovelace' — untouched
print(name[::-1]) # 'ecalevol ada' — reversed via a slice
Next steps
You have the mental model — now put it to work. Browse runnable Python string examples for copy-paste snippets, follow how to reverse a string for a classic task, or read the focused references for str.split() and str.replace(). When you are ready for the next collection type, start with Python lists — the basics. Every snippet runs in the online Python compiler or the full editor.
Frequently asked questions
Are Python strings mutable?
How do I get the last character of a string?
s[-1] is the last character and s[-2] the one before it. The last position is also s[len(s) - 1] because indexing starts at 0.What is an f-string in Python?
f where {expression} is replaced by the value inside, e.g. f"Hi {name}". It is the clearest way to build a string from other values.How do I check whether a string contains another string?
in operator for a True/False answer, e.g. "cat" in text. Use text.find("cat") when you need the position; it returns -1 when the substring is not present.