Slicing is Python's way of extracting a piece of a sequence — a portion of a string, or a section of a list — using a clean, readable notation. Instead of writing a loop to pull out characters one by one, a slice lets you say exactly which part you want in a single expression. Slicing is one of Python's most elegant features: once you understand it, you will use it constantly.
1 Indexing: One Item at a Time
Before slicing makes sense, you need to be comfortable with indexing — accessing a single item from a sequence using its position number. Every character in a string and every item in a list has an index: a number that says where it sits.
Imagine a row of seats in a cinema. Each seat has a number. To find someone, you just say their seat number — "seat 3" — and you go directly there without checking every seat. Indexing works the same way: you give Python a position number and it goes straight to that item.
1.1 Zero-Based Indexing
Python counts positions starting from zero, not one. The first item is at index 0, the second at index 1, and so on. This is called zero-based indexing and it is used in almost every programming language.
| character | P | y | t | h | o | n |
| positive index | 0 | 1 | 2 | 3 | 4 | 5 |
| negative index | -6 | -5 | -4 | -3 | -2 | -1 |
language = "Python" print(language[0]) # P — first character print(language[1]) # y print(language[5]) # n — last character
If you ask for an index that does not exist — for example language[10] on a 6-character string — Python raises an IndexError: string index out of range. Valid indices for a string of length n run from 0 to n-1.
1.2 Negative Indices: Counting From the End
Python also lets you count backwards from the end using negative numbers. Index -1 is always the last item, -2 is second-to-last, and so on. This is incredibly useful when you want the end of a sequence but you do not know how long it is.
language = "Python" print(language[-1]) # n — last character print(language[-2]) # o — second to last print(language[-6]) # P — same as [0]
Imagine you have a filename like "report_2026_final.csv" and you want the last four characters (the extension). You could count the characters and use a specific index — but what if the filename changes length? With negative indexing, filename[-4:] always gives you the last four characters, no matter how long the name is. You will see this pattern everywhere.
- What index does Python use for the first item in a sequence? Why does it start there instead of 1?
- Given
word = "banana", what is the value ofword[2]? What aboutword[-1]? - A string has 8 characters. What are the valid positive indices? What is the index of the last character?
- Without running the code, what does
"hello"[-3]return? Explain how you worked it out.
2 What Is a Slice?
An index gives you one item. A slice gives you a range of items — a section of the sequence from one position to another, returned as a new string or list.
A whole baguette is your sequence. You can pick up one piece (indexing), or you can cut out a section — "from the third mark to the seventh mark" — and lift out that portion (slicing). The original baguette is unchanged; you have just taken a piece. Python slices work exactly this way: the original string or list is never modified; a new, smaller copy is created.
2.1 Basic Slice Notation
A slice is written with square brackets and a colon separating two positions:
sequence[start : stop]
start— the index of the first item to include.stop— the index of the first item not to include. The slice stops before this position.
language = "Python" # 0 1 2 3 4 5 print(language[0:3]) # Pyt — indices 0, 1, 2 (stops before 3) print(language[2:5]) # tho — indices 2, 3, 4 (stops before 5) print(language[1:4]) # yth — indices 1, 2, 3 (stops before 4)
language[0:3] gives you indices 0, 1, and 2 — not index 3. The stop value is a boundary marker, not the last item to include. Think of it as: "start at start, keep going, stop when you reach stop." You never pick up the item at stop itself.
This is consistent with how range(start, stop) works — and for the same reason. Once you expect it, it feels natural. Until then, it is the single most common slicing mistake.
2.2 Visualising the Slice
A helpful mental model: think of the indices as sitting between the characters, like fence posts between fence panels. A slice cuts between two fence posts and lifts out everything between them.
| character | P | y | t | h | o | n | |
| cut positions → | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
[0:3] cuts at position 0 and position 3, lifting out Pyt. [2:5] cuts at position 2 and position 5, lifting out tho. Position 6 is one past the end — that is why [0:6] gives you the entire string.
3 Shorthand: Omitting start or stop
Python lets you leave out either — or both — values in a slice. When you omit a value, Python uses a sensible default:
| Notation | Meaning | Example ("Python") | Result |
|---|---|---|---|
[ : stop] | From the very beginning up to (not including) stop. | "Python"[:3] | "Pyt" |
[start : ] | From start all the way to the end. | "Python"[2:] | "thon" |
[ : ] | The entire sequence — a full copy. | "Python"[:] | "Python" |
word = "Python" print(word[:3]) # Pyt — first 3 characters print(word[3:]) # hon — everything from index 3 onward print(word[:]) # Python — a full copy
word[:3] is cleaner and more readable than word[0:3]. When start is 0 or stop is the end of the sequence, omitting that value signals your intent clearly: "I want everything from the beginning" or "I want everything to the end." You will see this shorthand constantly in real Python code.
Given
text = "abcdefgh", predict the result of each slice:text[2:5] text[:4] text[5:] text[:]
- Why does
"Python"[0:3]return"Pyt"and not"Pyth"? - Without running it, what does
"hello world"[6:]return? - A student wants the first 5 characters of a string called
title. Write two different slice expressions that both produce the same result.
4 Negative Indices in Slices
Just like with single-item indexing, you can use negative numbers in slices to count from the end. This is especially powerful when you do not know the length of the sequence in advance.
filename = "report_2026_final.csv" print(filename[-4:]) # .csv — the last 4 characters (the extension) print(filename[:-4]) # report_2026_final — everything except the extension print(filename[-8:-4]) # inal — 4 characters, 8 from the end to 4 from the end
A ruler lets you measure from the left (positive indices) or from the right (negative indices). A slice can mix both: start from a position on the left, stop at a position measured from the right. The cut is still just two positions on the same ruler — one from each end.
phrase = "Hello, World!" # 0123456789... print(phrase[-6:]) # orld! — last 6 characters print(phrase[:-1]) # Hello, World — everything except the final ! print(phrase[-6:-1]) # orld — 5 characters near the end
- Given
email = "[email protected]", write a slice that extracts just"school.edu"using a negative start index. How many characters from the end does it start? - What does
"abcdefgh"[-3:]return? What about"abcdefgh"[:-3]? - A filename always ends with a 4-character extension including the dot (e.g.
.txt,.csv,.jpg). Write a slice that extracts just the name (everything before the extension) for any filename. Predict the output:
s = "programming" print(s[-4:]) print(s[:-7]) print(s[-7:-4])
5 The Third Value: Step
The full slice notation actually has a third optional value — the step. It tells Python how many positions to jump between each item it picks up:
sequence[start : stop : step]
When you write [0:6], the step defaults to 1 — pick up every item. With [0:6:2], the step is 2 — pick up every other item.
5.1 Step in Action
letters = "abcdefgh" print(letters[::2]) # aceg — every other character (step 2) print(letters[1::2]) # bdfh — starting from index 1, every other print(letters[::3]) # adg — every third character print(letters[0:8:2]) # aceg — explicit start/stop with step 2
5.2 Reversing with Step -1
The most famous use of the step value is reversing a sequence. A step of -1 means "go backwards one position at a time." Combined with omitting start and stop, it reverses the entire sequence:
word = "Python" print(word[::-1]) # nohtyP — the word reversed numbers = [1, 2, 3, 4, 5] print(numbers[::-1]) # [5, 4, 3, 2, 1]
[::-1] is a Python idiom — a well-known, widely used shorthand that every Python programmer recognises immediately. It is worth memorising.
When step is negative, Python starts from the end and works backwards. Omitting start defaults to "the last position" and omitting stop defaults to "before the beginning." So [::-1] means: "start at the end, stop at the beginning (exclusive), step backwards by 1 each time" — which produces the sequence in reverse.
A step of 0 would mean "don't move" — Python would be stuck in place forever. If you write [::0], Python raises a ValueError: slice step cannot be zero. Any non-zero integer is valid as a step.
- What does the step value in a slice control?
Predict the output of each of these:
s = "abcdefghij" print(s[::2]) print(s[1::2]) print(s[::-1]) print(s[8🔢-1])
- Write a single slice expression that reverses the string
"racecar". What do you notice about the result? - A student wants every third item from a list starting at index 0. Write the slice.
- True or False:
[::-1]modifies the original string. Explain your answer.
6 Slicing Lists
Everything you have learned about slicing strings applies equally to lists. The notation is identical — the only difference is that you get back a list instead of a string.
scores = [85, 42, 91, 67, 55, 78, 39, 95] # 0 1 2 3 4 5 6 7 print(scores[0:3]) # [85, 42, 91] — first three scores print(scores[3:]) # [67, 55, 78, 39, 95] — from index 3 onward print(scores[:3]) # [85, 42, 91] — same as [0:3] print(scores[-3:]) # [78, 39, 95] — last three scores print(scores[::2]) # [85, 91, 55, 39] — every other score print(scores[::-1]) # [95, 39, 78, 55, 67, 91, 42, 85] — reversed
6.1 Slices Create New Lists — The Original Is Unchanged
A slice never modifies the original. It creates a brand-new sequence containing the selected items. This means you can slice freely without worrying about damaging your data:
scores = [85, 42, 91, 67, 55] top_three = scores[:3] # a new list — [85, 42, 91] bottom_two = scores[-2:] # a new list — [67, 55] print(scores) # [85, 42, 91, 67, 55] — unchanged print(top_three) # [85, 42, 91] print(bottom_two) # [67, 55]
6.2 Practical List Slicing
students = ["Alice", "Bob", "Carol", "Dan", "Eve", "Fran"]
# First half vs second half
midpoint = len(students) // 2
group_a = students[:midpoint]
group_b = students[midpoint:]
print("Group A:", group_a) # ['Alice', 'Bob', 'Carol']
print("Group B:", group_b) # ['Dan', 'Eve', 'Fran']
# Skip the first and last (sometimes used to ignore header/footer rows)
middle = students[1:-1]
print("Middle:", middle) # ['Bob', 'Carol', 'Dan', 'Eve']Given
nums = [10, 20, 30, 40, 50, 60, 70, 80], predict the result of each slice:nums[2:5] nums[:4] nums[-3:] nums[::3] nums[::-1]
After this code runs, what are the values of
original,first_half, andsecond_half?original = [1, 2, 3, 4, 5, 6] first_half = original[:3] second_half = original[3:] first_half[0] = 99 print(original)
Does modifying
first_halfaffectoriginal? Why or why not?- Write a slice that extracts the last 4 items from any list called
data, regardless of how long it is. - You have a list of 100 exam results. Write a slice that gives you every fifth result, starting from the first.
7 An Important Difference: Strings vs. Lists
Slicing behaves identically on strings and lists — with one crucial difference: strings are immutable. You cannot change a character inside a string. Lists are mutable — you can change, add, and remove items.
# Strings — immutable, cannot change individual characters name = "Python" name[0] = "J" # TypeError: 'str' object does not support item assignment # To "change" a string, you must build a new one using slices: name = "J" + name[1:] print(name) # Jython
# Lists — mutable, individual items can be changed directly colours = ["red", "green", "blue"] colours[1] = "yellow" print(colours) # ['red', 'yellow', 'blue'] # You can also replace a slice of a list: colours[0:2] = ["pink", "purple"] print(colours) # ['pink', 'purple', 'blue']
Strings in Python are immutable — once created, their characters cannot be changed. Attempting to do so raises a TypeError. This is not a bug; it is a deliberate design choice that makes strings safe to share across a program without unexpected side effects.
If you need to modify a string, the pattern is: use slicing to take the parts you want, combine them with the new content using +, and assign the result to the variable. The "new" string is actually a brand-new object — the old one is untouched.
- What does "immutable" mean? Which type is immutable — strings or lists?
This code raises an error. Why? How do you fix it to produce
"Hello, World!"?greeting = "hello, World!" greeting[0] = "H" print(greeting)
- Given
items = ["a", "b", "c", "d", "e"], what doesitems[1:4] = ["X", "Y"]do to the list? Predict the result, then verify.
8 Common Slice Patterns Worth Memorising
These patterns come up so frequently in real Python code that they are worth learning by name, just like the loop patterns in Article 5.
| Pattern | Slice | What it does | Example |
|---|---|---|---|
| First N items | [:n] | The first n items. | "Python"[:3] → "Pyt" |
| Last N items | [-n:] | The last n items. | "Python"[-3:] → "hon" |
| Everything except first N | [n:] | Skip the first n items. | "Python"[2:] → "thon" |
| Everything except last N | [:-n] | Skip the last n items. | "Python"[:-2] → "Pyth" |
| Reverse | [::-1] | The entire sequence, backwards. | "Python"[::-1] → "nohtyP" |
| Every other item | [::2] | Items at even indices (0, 2, 4…) | "Python"[::2] → "Pto" |
| Full copy | [:] | A new copy of the entire sequence. | scores[:] → copy of scores |
| File extension | [-4:] | Last 4 characters — assumes a 3-char extension + dot. | "data.csv"[-4:] → ".csv" |
9 Putting It All Together
Here is a program that uses slicing in several realistic ways. Read it and predict the output before running it.
def is_palindrome(word):
"""Returns True if word reads the same forwards and backwards."""
return word.lower() == word.lower()[::-1]
def file_extension(filename):
"""Returns the file extension including the dot."""
dot_index = filename.rfind(".") # find the last dot
return filename[dot_index:]
def truncate(text, max_length):
"""Shortens text to max_length characters, adding '...' if needed."""
if len(text) <= max_length:
return text
return text[:max_length] + "..."
def initials(full_name):
"""Returns initials from a full name, e.g. 'Ada Lovelace' -> 'A.L.'"""
parts = full_name.split() # split into a list of words
return ".".join(word[0] for word in parts) + "."
# ── Testing each function ─────────────────────────────────────────
test_words = ["racecar", "python", "level", "hello"]
for word in test_words:
result = "✓ palindrome" if is_palindrome(word) else "✗ not a palindrome"
print(f" {word:<10} {result}")
print()
filenames = ["report.docx", "data_2026.csv", "image.final.png"]
for f in filenames:
print(f" {f:<25} extension: {file_extension(f)}")
print()
long_text = "The quick brown fox jumped over the lazy dog"
print(truncate(long_text, 20))
print(truncate("Short text", 20))
print()
names = ["Ada Lovelace", "Alan Turing", "Grace Hopper"]
for name in names:
print(f" {name:<20} initials: {initials(name)}")Slicing at work in this program:
word.lower()[::-1]— reverses the lowercased word to check for palindromes.filename[dot_index:]— slices from the dot to the end to get the extension.text[:max_length]— takes the firstmax_lengthcharacters for truncation.word[0]— single-character index to get each word's first letter for initials.
10 Final Check: The Big Picture
Slicing extracts a portion of a string or list using the notation [start : stop : step]. The stop index is never included. Omitting start defaults to the beginning; omitting stop defaults to the end. Negative indices count from the end. A step of -1 reverses a sequence. Slices never modify the original — they always return a new copy. Strings are immutable and cannot be changed in place; lists are mutable and can be. Knowing the common slice patterns by name makes your code shorter, cleaner, and more readable.
Given
s = "abcdefghij"(10 characters), predict the result of every expression in this table before running anything:Expression Your prediction s[3] s[-2] s[2:6] s[:4] s[7:] s[-4:] s[:-3] s[::2] s[::-1] s[1:8:3] - A student writes
word = "elephant"and wants the middle four characters ("pha"... wait, let them figure it out). What slice gives you characters at positions 2, 3, 4, and 5? Write it two different ways — once with positive indices, once with negative. This code is supposed to check whether a string is a palindrome, but it has a bug. Find it and fix it.
def is_palindrome(s): return s == s[-1👎-1] print(is_palindrome("racecar")) # should be True print(is_palindrome("hello")) # should be False- Write a function called
middle(sequence)that returns everything except the first and last items of any string or list. For example,middle("Python")should return"ytho", andmiddle([1,2,3,4,5])should return[2,3,4]. Explain in plain English why this does not work, and how to fix it:
name = "alice" name[0] = "A" print(name)
11 Quick-Reference Summary
| Notation | What it means | Example → Result |
|---|---|---|
s[i] | Single item at index i. | "Python"[1] → "y" |
s[-i] | Single item, i positions from the end. | "Python"[-1] → "n" |
s[start:stop] | Items from start up to (not including) stop. | "Python"[1:4] → "yth" |
s[:stop] | From the beginning up to (not including) stop. | "Python"[:3] → "Pyt" |
s[start:] | From start to the end. | "Python"[3:] → "hon" |
s[:] | Full copy of the sequence. | "Python"[:] → "Python" |
s[start:stop:step] | Every step-th item from start to stop. | "Python"[::2] → "Pto" |
s[::-1] | The entire sequence reversed. | "Python"[::-1] → "nohtyP" |
s[-n:] | The last n items. | "Python"[-3:] → "hon" |
s[:-n] | Everything except the last n items. | "Python"[:-2] → "Pyth" |