Slicing Lists & Strings
Reviewed & published by Brayan K
Slicing is the syntax seq[start:stop:step] for extracting a new sub-sequence — a range of elements from a list, string, or tuple — without writing a loop.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Once you can grab "the first three", "everything after the second", or "this reversed" in a single bracket expression, you'll reach for slicing constantly. It's one of the most distinctly Pythonic tools you'll learn.
What You'll Learn in This Lesson
- The start:stop:step slice syntax
- Why the stop index is excluded (half-open ranges)
- Negative indices to count from the end
- Omitting bounds for 'from the start' / 'to the end'
- Reversing any sequence with [::-1]
- Shallow-copying and slice assignment on lists
1 start : stop — Grabbing a Range
A slice asks for everything from start up to but not including stop. The same syntax works on lists and strings:
letters = ["a", "b", "c", "d", "e", "f"]
# 0 1 2 3 4 5
print(letters[1:4]) # ['b', 'c', 'd'] -> indexes 1, 2, 3
print(letters[0:2]) # ['a', 'b'] -> stop (2) excluded
text = "Python"
print(text[0:3]) # 'Pyt'
print(text[2:5]) # 'tho'📐 How to read the indexes:
a b c d e f
0 1 2 3 4 5 <- start counts here
-6 -5 -4 -3 -2 -1 <- negatives count from the endThink of indexes as sitting between the items. letters[1:4] takes the three elements between position 1 and position 4.
# Basic start:stop slicing on a list and a string
nums = [10, 20, 30, 40, 50, 60, 70]
word = "slicing"
# First three numbers
print(nums[0:3]) # [10, 20, 30]
# The middle chunk
print(nums[2:5]) # [30, 40, 50]
# A piece of the word
print(word[1:4]) # 'lic'
# stop past the end just clamps - no error
print(nums[5:100]) # [60, 70]
# ✅ Expected output:
# [10, 20, 30]
# [30, 40, 50]
# lic
# [60, 70]✂️ Omitting Bounds & Negative Indices
Leave out start to mean "from the beginning" and leave out stop to mean "to the end". Negative numbers count back from the last item:
data = [10, 20, 30, 40, 50]
print(data[:3]) # [10, 20, 30] -> start defaults to 0
print(data[2:]) # [30, 40, 50] -> stop defaults to the end
print(data[:]) # [10, 20, 30, 40, 50] -> a full copy
# Negative indices count from the end
print(data[-1]) # 50 -> last item
print(data[-2:]) # [40, 50] -> last two
print(data[:-1]) # [10, 20, 30, 40] -> everything but the last
# Mix them freely
print(data[1:-1]) # [20, 30, 40] -> drop first AND lastdata[:-1] ("all but the last") and data[-2:] ("the last two") are everyday idioms — worth memorizing.
# Omitting bounds and using negative indices
path = "report_2024.csv"
# Drop the last 4 characters (the ".csv")
print(path[:-4]) # 'report_2024'
# Keep only the extension
print(path[-3:]) # 'csv'
scores = [88, 92, 75, 60, 95, 70]
print("First two:", scores[:2]) # [88, 92]
print("Last two:", scores[-2:]) # [95, 70]
print("Middle:", scores[1:-1]) # [92, 75, 60, 95]
# ✅ Expected output:
# report_2024
# csv
# First two: [88, 92]
# Last two: [95, 70]
# Middle: [92, 75, 60, 95]2 The step — Every Nth Item & Reversing
The third number is the step: how far to jump each time. A step of 2 takes every other item; a negative step walks backwards:
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(nums[::2]) # [0, 2, 4, 6, 8] -> every 2nd item
print(nums[1::2]) # [1, 3, 5, 7, 9] -> odds, starting at 1
print(nums[::3]) # [0, 3, 6, 9] -> every 3rd item
# A negative step reverses
print(nums[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
text = "racecar"
print(text[::-1]) # 'racecar' (a palindrome!)
print("hello"[::-1]) # 'olleh'# step controls the stride; -1 reverses
digits = [1, 2, 3, 4, 5, 6, 7, 8]
print("Every 2nd:", digits[::2]) # [1, 3, 5, 7]
print("Every 3rd:", digits[::3]) # [1, 4, 7]
print("Reversed:", digits[::-1]) # [8, 7, 6, 5, 4, 3, 2, 1]
# A quick palindrome check using a reversed slice
def is_palindrome(s):
return s == s[::-1]
print(is_palindrome("level")) # True
print(is_palindrome("python")) # False
# ✅ Expected output:
# Every 2nd: [1, 3, 5, 7]
# Every 3rd: [1, 4, 7]
# Reversed: [8, 7, 6, 5, 4, 3, 2, 1]
# True
# False🧩 Slice Copies & Slice Assignment
Reading a slice makes a copy
A slice never touches the original — seq[:] is the classic shallow-copy trick:
original = [1, 2, 3]
copy = original[:] # a separate list with the same items
copy.append(4)
print(original) # [1, 2, 3] -> untouched
print(copy) # [1, 2, 3, 4]Assigning TO a slice edits in place
Put a slice on the left of = to replace that whole section — the replacement can even be a different length:
nums = [10, 20, 30, 40, 50]
nums[1:3] = [99, 99] # replace indexes 1-2
print(nums) # [10, 99, 99, 40, 50]
nums[1:3] = [0] # shrink (2 items -> 1)
print(nums) # [10, 0, 40, 50]
nums[1:1] = [7, 8] # insert without removing
print(nums) # [10, 7, 8, 0, 40, 50]🎯 Your Turn: Fill in the Blanks
Replace each ___ so the slices produce the commented output. Think about which bound or step you need.
# 🎯 YOUR TURN: replace each ___ to match the expected output
seq = [10, 20, 30, 40, 50, 60]
# 1. The first three items -> [10, 20, 30]
print(seq[___])
# 2. The last two items -> [50, 60]
print(seq[___])
# 3. The whole list reversed -> [60, 50, 40, 30, 20, 10]
print(seq[___])
# 4. Every other item -> [10, 30, 50]
print(seq[___])
# Expected output:
# [10, 20, 30]
# [50, 60]
# [60, 50, 40, 30, 20, 10]
# [10, 30, 50]print(seq[:3]) # [10, 20, 30]
print(seq[-2:]) # [50, 60]
print(seq[::-1]) # [60, 50, 40, 30, 20, 10]
print(seq[::2]) # [10, 30, 50]Common Errors & How to Fix Them
❌ Expecting the stop index to be included
"abcd"[0:2] # you might expect 'abc' ... it's 'ab'✅ The stop is excluded. To include index 2, use [0:3]. Remember: stop = "first index NOT taken".
❌ Mixing up index errors and slice clamping
nums = [1, 2, 3]
nums[10] # IndexError!
nums[1:10] # fine -> [2, 3]✅ A single index out of range raises IndexError; a slice out of range simply clamps to what exists.
❌ Reversing with a positive step
[1, 2, 3][3:0] # [] (empty) - going backwards needs a -step✅ To go right-to-left you need a negative step: [1, 2, 3][::-1] gives [3, 2, 1].
🎯 Mini Challenge: Filename Surgery
Use only slicing to pull apart a filename and a CSV row. No split() allowed!
# Mini Challenge: slice a filename and a fixed-width record
filename = "invoice_0042.pdf"
# 1. The base name without extension -> 'invoice_0042'
base = filename[:-4]
print("Base:", base)
# 2. The extension without the dot -> 'pdf'
ext = filename[-3:]
print("Ext:", ext)
# 3. Just the 4-digit number -> '0042'
number = filename[8:12]
print("Number:", number)
# 4. The base, reversed -> '2400_eciovni'
print("Reversed:", base[::-1])
# Expected output:
# Base: invoice_0042
# Ext: pdf
# Number: 0042
# Reversed: 2400_eciovni📚 Keep Learning
Go deeper with the official Python documentation:
- Python Tutorial — Lists & Slicing — the introduction's section on indexing and slicing.
- Common Sequence Operations — the formal rules every sequence (list, str, tuple) shares.
- Language Reference — Slicings — the precise grammar of the slice syntax.
Frequently Asked Questions
Lesson complete — you slice like a pro!
You can grab ranges with start:stop, count from the end with negatives, stride or reverse with step, copy with [:], and rewrite sections with slice assignment. These tricks return again and again in real code.
Practice quiz
What is the full slice syntax?
- seq[start, stop, step]
- seq(start:stop:step)
- seq[start:stop:step]
- seq{start:stop:step}
Answer: seq[start:stop:step]. Slicing uses square brackets with colons: seq[start:stop:step].
Given letters = ['a','b','c','d','e','f'], what is letters[1:4]?
- ['b','c','d']
- ['a','b','c','d']
- ['b','c','d','e']
- ['a','b','c']
Answer: ['b','c','d']. Indexes 1, 2, 3 are taken; stop (4) is excluded, giving ['b','c','d'].
Why is the stop index excluded in a slice?
- It's a bug in Python
- Stop is always included
- Only for strings
- Slices are half-open: stop is the first index NOT included
Answer: Slices are half-open: stop is the first index NOT included. Slices are half-open, so the length is stop minus start and seq[:i]+seq[i:] rebuilds the original.
What does 'Python'[2:5] return?
- 'yth'
- 'tho'
- 'thon'
- 'ytho'
Answer: 'tho'. Indexes 2,3,4 of 'Python' are t, h, o, giving 'tho'.
What does data[:3] mean?
- From the start up to (not including) index 3
- From index 3 to the end
- Every 3rd item
- The last 3 items
Answer: From the start up to (not including) index 3. Omitting start defaults to 0, so data[:3] takes the first three items.
What does data[-2:] return?
- Everything but the last two
- The second item
- The last two items
- An empty list
Answer: The last two items. Negative indices count from the end; -2: gives the last two items.
What does seq[::-1] do?
- Removes the last item
- Returns a reversed copy of the sequence
- Returns every other item
- Raises an error
Answer: Returns a reversed copy of the sequence. A step of -1 walks backwards over the whole sequence, returning a reversed copy.
What does nums[::2] return for [0,1,2,3,4,5]?
- [1,3,5]
- [0,1,2]
- [2,4]
- [0,2,4]
Answer: [0,2,4]. A step of 2 starting at 0 takes every other item: [0,2,4].
What is seq[:] commonly used for?
- Deleting a list
- Making a shallow copy of the sequence
- Reversing a list
- Sorting
Answer: Making a shallow copy of the sequence. seq[:] produces a brand-new copy and leaves the original untouched.
What happens with nums[10] vs nums[1:10] on [1,2,3]?
- Both raise IndexError
- Both clamp to what exists
- nums[10] raises IndexError; nums[1:10] clamps to [2,3]
- nums[1:10] raises IndexError
Answer: nums[10] raises IndexError; nums[1:10] clamps to [2,3]. A single out-of-range index raises IndexError, but an out-of-range slice clamps to what exists.
Continue this course
- Previous: Architecture Patterns for Python Apps (MVC, Clean Architecture)
- Next: Booleans & Truthiness — Truthy/falsy values, short-circuit and/or, and any()/all()
- Quick reference: Python cheat sheet › Strings
Frequently asked questions
Why is the stop index excluded in a slice?
Python slices are half-open: seq[start:stop] includes start but stops just before stop. This design has a neat payoff — the length of the slice is simply stop minus start, and seq[:i] plus seq[i:] always rebuilds the original with no overlap and no gap. Once you internalize 'stop is the first index NOT included', off-by-one mistakes mostly disappear.
What's the difference between a slice and an index?
An index like seq[2] returns a single element. A slice like seq[2:5] returns a new sub-sequence of the same type (a list from a list, a string from a string). Indexing out of range raises IndexError; slicing out of range never errors — it just clamps to what exists and may return an empty sequence.
Does slicing change the original list?
No — reading a slice always produces a brand-new object and leaves the original untouched. seq[:] is in fact the classic way to make a shallow copy of a list. The only way slicing mutates is when you assign TO a slice, like nums[1:3] = [9, 9], which replaces that section in place.
How does [::-1] reverse a sequence?
The third number in a slice is the step. A step of -1 walks the sequence backwards, and with both bounds omitted it spans the whole thing, so seq[::-1] returns a reversed copy. It works identically on strings and lists, which makes it the shortest idiom for 'give me this reversed'.