Mastering String Slicing
Introduction: From One Character to a Whole Chunk
Welcome back! We know how to reach any single character inside a string. Useful as that is, real text work rarely stops at one character. We usually want a piece of text: the name part of a filename, its final few characters, or the year buried inside a timestamp.
Python's tool for that job is slicing, written as text[start:stop:step]. Same square brackets as before, just with colons separating up to three numbers. Our running example is a filename:
| Character | r | e | p | o | r | t | _ | 2 | 0 | 2 | 4 | . | c | s | v |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| Negative | -15 | -14 | -13 | -12 | -11 | -10 | -9 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 |
Basic Slice Syntax: [start:stop]
Let's start with the two-number form, asking for characters from position 0 up to position 6:
The single most important rule of slicing is that start is included and stop is excluded. So filename[0:6] collects indices 0 through 5, which spell "report"; index 6, the underscore, stays out. A handy side effect is that the length of the result equals stop - start — here, six characters.
Two more details: a slice builds a new string and leaves filename untouched, and a backward range such as filename[4:2] simply gives an empty string "" rather than an error.
Spelling Out Both Bounds
The two-number form really shines when the piece we want sits in the middle, with text on both sides. The year in our filename is a good example: it starts at index 7 and we want four characters, so we stop at 7 + 4 = 11.
This collects indices 7, 8, 9, and 10 — the characters 2, 0, 2, 4 — giving "2024". Note how the exclusive stop works for us here: 11 is the index of the dot, and naming it as the stop keeps it out of the result. A useful way to picture it is that the numbers mark the gaps between characters rather than the characters themselves, so [7:11] means "everything between gap 7 and gap 11."
Omitting Bounds to Reach the Edges
Writing 0 as the start feels redundant, and Python agrees: either bound can be left out, and it snaps to the nearest edge of the string. No start means "from the very beginning," no stop means "through the very end."
This gives us the classic prefix/suffix pair:
filename[:6]is identical tofilename[0:6], so we get the prefix"report"with less typing.filename[7:]starts at index7, the2in2024, and runs all the way to the finalv, producing"2024.csv".
Leaving out both bounds, as in filename[:], asks for everything: a full copy of the string.
Negative Indices Inside Slices
As you may recall from Unit 1, negative indices count backward from the right, with -1 as the last character. Those same numbers work as slice bounds, which makes end-of-string work delightfully short:
filename[-3:] starts three characters from the end and runs through the end, giving "csv". Two reusable patterns are worth memorizing:
text[-N:]means "the last N characters."text[:-N]means "everything except the last N characters," sofilename[:-4]gives"report_2024".
Neither form needs len() first, so the same expression works for "a.csv" or a filename with fifty characters.
An honest caveat about file extensions. For this filename, the last three characters happen to be the extension, csv. But that is a fact about our example, not a general rule: "script.py" has a two-character extension, "photo.jpeg" has four, "README" has none at all, and "data.tar.gz" has two suffixes stacked. [-3:] would quietly return the wrong text in every one of those cases. Real code finds the dot instead of assuming a width — in Unit 4 we will meet find(), which locates a character for us, and Python's standard library offers pathlib.Path(filename).suffix for exactly this job. For now, treat [-3:] as "the last three characters," which is all it really promises.
Adding a Step: [start:stop:step]
Now for the third slot in text[start:stop:step]. The step value tells Python how far to jump after taking each character; it defaults to 1, which is why every slice so far picked up neighbours. Setting it to 2 takes every second character instead:
Both bounds are omitted, so we walk the whole string, and the step of 2 visits indices 0, 2, 4, 6, 8, 10, 12, and 14. Reading those off the table gives r, p, r, _, 0, 4, c, v, joined into "rpr_04cv". A step of 3 would visit 0, 3, 6, and so on; any positive whole number works.
Reversing with a Negative Step
A step can also be negative, which flips the direction of travel: Python walks the string from right to left. This turns string reversal into a single expression:
Starting from the last character, v, and stepping backward one position at a time, we collect every character in reverse order, ending on the leading r. Here is the gotcha worth remembering: with a negative step the default bounds flip too, so an omitted start means "the end of the string" and an omitted stop means "past the beginning."
That is why [::-1] is the reliable reversal idiom, while a hopeful guess such as filename[0:15:-1] returns an empty string — we would be asking Python to move left from index 0, and there is nothing there.
Slice Patterns Cheat Sheet
Most day-to-day slicing comes down to a handful of shapes:
| Pattern | Meaning | Example on filename |
|---|---|---|
[a:b] | From a up to, but not including, b | [7:11] gives "2024" |
[:n] | The first n characters | [:6] gives "report" |
[n:] | Everything from n to the end | [7:] gives "2024.csv" |
[-n:] | The last n characters | [-3:] gives "csv" |
[:-n] | Everything except the last n | [:-4] gives "report_2024" |
[::k] | Every k-th character | [::2] gives "rpr_04cv" |
[::-1] | The whole string reversed | "vsc.4202_troper" |
Recognizing these shapes on sight is the real goal; the rest is filling in numbers that fit the task at hand.
Conclusion and Next Steps
One more comforting property before we practise: unlike indexing, slicing never raises an out-of-range error. It quietly clips to what exists, so filename[:500] returns the whole string rather than crashing, and even an empty string survives every slice in the table above.
In Unit 3 we shift from carving text apart to reshaping it with string methods such as upper, strip, and replace. Time to slice!
