Python Tutorial
Python Slicing Strings
Slicing pulls a section out of a string with [start:end:step]. Two ideas unlock everything: the end index is not included, and negative numbers count from the right.
Basic slice: [start:end]
Give a start and an end index. You get the characters from start up to — butnot including — end. Indexing begins at 0.
Example
b = "Hello, World!"
# 0123456789...
print(b[2:5]) # llo (positions 2, 3, 4 — position 5 is excluded)Why exclude the end? Because it makes the length easy to read off: b[2:5] is5 - 2 = 3 characters. And two slices like b[:5] and b[5:] fit together perfectly with no gap or overlap.
Leave out the start or the end
Omit start to begin at the very first character; omit end to run to the last one.
b = "Hello, World!"
print(b[:5]) # Hello (start defaults to 0)
print(b[7:]) # World! (end defaults to the length)
print(b[:]) # Hello, World! (a full copy)Negative indexing
Negative numbers count from the end: -1 is the last character, -2 the second-to-last, and so on. Handy when you care about the end of a string but don't know its length.
b = "Hello, World!"
print(b[-6:-1]) # World (from 6th-from-last up to, not incl., last)
print(b[-1]) # ! (single index, not a slice)
print(b[-6:]) # World!The step: [start:end:step]
A third number sets the stride. 2 takes every second character; a negative step walks backwards — which gives Python's famous one-line string reversal.
b = "Hello, World!"
print(b[::2]) # Hlo ol! (every 2nd character)
print(b[::-1]) # !dlroW ,olleH (reversed)
print(b[6:1:-1]) # ,oll (walk left from index 6 down to 2)With a negative step, start should be to the right of end, or you get an empty string. "abc"[0:3:-1] is "" because you can't walk right-to-left from 0 to 3.
Out-of-range slices are safe
Slicing never raises an "index out of range" error — Python quietly clamps to what exists. (A single index likeb[100] does raise; a slice like b[2:100] does not.)
b = "Hello, World!"
print(b[7:999]) # World! (clamped to the end, no error)
print(b[50:60]) # (empty string)Slicing returns a new string
Every slice is a fresh copy — the original is never modified (strings are immutable anyway). This is why slicing is a safe way to inspect or reshape text without side effects.
word = "python"
first_three = word[:3]
print(first_three) # pyt
print(word) # python (untouched)Real uses of slicing
Grab a file extension
name = "report.pdf"
print(name[-3:]) # pdf
# more robust when the length varies:
print(name.rsplit(".", 1)[-1]) # pdfBuild initials
full = "Ada Lovelace"
initials = "".join(part[0] for part in full.split())
print(initials) # ALCheck a palindrome
s = "racecar"
print(s == s[::-1]) # TrueQuick reference
| Slice | Meaning | On "Hello, World!" |
|---|---|---|
[2:5] | Index 2 up to (not incl.) 5 | llo |
[:5] | Start through index 4 | Hello |
[7:] | Index 7 to the end | World! |
[-6:] | Last 6 characters | World! |
[::2] | Every 2nd character | Hlo ol! |
[::-1] | Reversed | !dlroW ,olleH |
Try It Yourself
Exercise: Given code = "PY-2024-0042", slice out the year 2024 and, separately, the last four digits 0042.
Show solution
code = "PY-2024-0042"
print(code[3:7]) # 2024
print(code[-4:]) # 0042Key Takeaways
[start:end]includesstart, excludesend.- Omit either side to go to the beginning or end; negative indexes count from the right.
- The third value is a step;
[::-1]reverses a string. - Slices clamp to range (no error) and always return a new string.
📘 Real-World Deep Dive
Slicing (<code>s[start:stop:step]</code>) works on every sequence — strings, lists, tuples, and even NumPy arrays. Knowing the off-by-one rules and the rare-but-useful <code>[::-1]</code> reverse is a daily multiplier.
Real-Life Scenario
A small parser that pulls out file extensions, identifies a class name from a fully-qualified Python name, and rotates a circular buffer of recent event names.
Real-Life Example
def ext_of(path: str) -> str:
dot = path.rfind(".")
return path[dot + 1:] if dot >= 0 and dot < len(path) - 1 else ""
def class_of(fqname: str) -> str:
# "pkg.sub.Mod.Class" -> "Class"
return fqname.rsplit(".", 1)[-1]
recent: list[str] = []
def remember(name: str, maxlen: int = 5) -> None:
recent.append(name)
if len(recent) > maxlen:
# keep only the last maxlen items in-place
del recent[:-maxlen]
paths = ["report.csv", "image.PNG.bak", "noext", "archive.tar.gz"]
for p in paths:
print(f"{p:<20} ext={ext_of(p)!r}")
for q in ["pkg.sub.Mod.Class", "TopLevel", "a.b.c"]:
print(f"{q:<25} class={class_of(q)!r}")
for name in ["a", "b", "c", "d", "e", "f", "g"]:
remember(name)
print(f"recent={list(recent)} last3={recent[-3:][-3:]} reversed={[x for x in reversed(recent)]}")Expected Output
report.csv ext='csv'
image.PNG.bak ext='bak'
noext ext=''
archive.tar.gz ext='gz'
pkg.sub.Mod.Class class='Class'
TopLevel class='TopLevel'
a.b.c class='c'
recent=['a'] last3=['a'] reversed=['a']
recent=['a', 'b']...
recent=['c', 'd', 'e'] last3=['c', 'd', 'e']...
recent=['d', 'e', 'f'] ...
recent=['e', 'f', 'g'] ...Common mistakes
- Confusing
stoppositions:xs[:3]is the first 3,xs[3:]is everything from the fourth onward. - Negative steps (
xs[::-1]) reverse the slice; combined with positive bounds (xs[::-2]) takes every other char in reverse. - Slicing past the end (
xs[:1000]) silently stops; uselen(xs)defensively.
🚀 Performance & Best Practices
- Slices create new objects — fine for strings, but
dequeis the right choice for sliding-window jobs. - Use
str.find(returns -1) instead ofstr.indexwhen the substring is optional. rsplit("/", 1)beats building a generic split + slice when you only care about the last element.
🧪 Try It Yourself
- Implement
rot_n(s, n)that rotates the firstnchars to the end using slicing alone. - Write
suffix_of(path, n)that returns the lastnchars without raising on short strings. - Benchmark
list(reversed(xs))vs.xs[::-1]for length-100 lists.