Sequence Slicing and Indexing
Master slicing and indexing on sequences in Python. This lesson covers accessing elements, slicing ranges, and applying these fundamentals in practical exercises.
Focus: use slicing and indexing on sequences
Imagine you’ve got a list of scores, a string of user input, or a tuple of coordinates — and you need to grab just the last three items, skip the first two, or reverse the whole sequence. Without slicing and indexing, you’d be stuck writing clumsy loops or multiple variable assignments. This lesson gives you the precise tools to extract exactly the data you want from any sequence, quickly and readably — an essential skill for every Python developer.
The problem this lesson solves
When you work with data in Python, you’ll constantly need to access specific elements or subsets of a sequence. Trying to do this without proper indexing leads to:
- Error-prone manual indices: Hardcoding
my_list[0],my_list[1], etc., makes code brittle. - Unreadable code: Looping and conditionally collecting items obscures intent.
- Performance waste: Creating copies of entire sequences when you only need a slice.
For example, getting the last five items of a 1000-element list by looping feels like using a sledgehammer to crack a nut. Slicing gives you a clean, one-line solution. This lesson solves the pain of clunky, manual data extraction and sets you up for efficient data manipulation.
Core concept / mental model
Think of a sequence (list, string, tuple) as a row of numbered lockers. Indexing lets you open one specific locker by its number. Slicing lets you open a range of lockers at once, say locker 2 through 5, and grab everything inside.
Definitions
- Index: An integer representing an element's position. Python uses zero-based indexing: the first element is at index
0, the second at1, and so on. - Slice: A contiguous subset of a sequence, specified by
start:stop:step. Thestopis exclusive — it goes up to, but does not include, that index. - Negative indexing: Index
-1refers to the last element,-2to the second last, etc. This makes it easy to access elements from the end without knowing the sequence’s length.
Pro Tip: Visualize indices as positions between elements. Index
0is before the first element. This helps understand whylst[0:2]gives elements at positions 0 and 1, not 0 through 2.
How it works step by step
Step 1: Basic indexing
Use square brackets [] with a single integer to access one element.
fruits = ['apple', 'banana', 'cherry', 'date']
first_fruit = fruits[0] # 'apple'
last_fruit = fruits[-1] # 'date'
second_last = fruits[-2] # 'cherry'
Step 2: Basic slicing
Syntax: sequence[start:stop] — returns a new sequence from index start up to (but not including) stop. If you omit start, it defaults to 0. If you omit stop, it goes to the end.
fruits = ['apple', 'banana', 'cherry', 'date']
first_two = fruits[0:2] # ['apple', 'banana']
middle_two = fruits[1:3] # ['banana', 'cherry']
from_start = fruits[:2] # ['apple', 'banana']
to_end = fruits[2:] # ['cherry', 'date']
Step 3: Including a step
Add a third integer step to skip elements: sequence[start:stop:step].
- A positive
stepmoves forward. - A negative
stepmoves backward (used to reverse). - Omitting
startandstopwith a negative step reverses the sequence.
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
every_second = numbers[::2] # [0, 2, 4, 6, 8]
reverse_all = numbers[::-1] # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
last_three_every_other = numbers[-1:-4:-2] # [9, 7]
Step 4: String slicing
Strings are sequences too! Slicing works exactly the same.
greeting = "Hello, World!"
substring = greeting[7:12] # 'World'
first_word = greeting[:5] # 'Hello'
reversed_str = greeting[::-1] # '!dlroW ,olleH'
Hands-on walkthrough
Let’s apply slicing and indexing in a practical scenario: processing a log file’s lines.
Basic indexing and slicing
log_lines = [
"2024-01-01 10:00:00 INFO User login",
"2024-01-01 10:01:00 WARN Disk 90%",
"2024-01-01 10:02:00 ERROR Out of memory",
"2024-01-01 10:03:00 INFO User logout"
]
# Access the error line
error_line = log_lines[2]
print(error_line) # Output: 2024-01-01 10:02:00 ERROR Out of memory
# Get the first two lines
first_two = log_lines[:2]
print(first_two)
# Output: ['2024-01-01 10:00:00 INFO User login', '2024-01-01 10:01:00 WARN Disk 90%']
# Get the last line
last_line = log_lines[-1]
print(last_line) # Output: 2024-01-01 10:03:00 INFO User logout
# Reverse the entire log
reversed_log = log_lines[::-1]
print(reversed_log)
Extracting timestamps with slicing
error_line = log_lines[2]
timestamp = error_line[:19] # '2024-01-01 10:02:00'
severity = error_line[20:25] # 'ERROR'
message = error_line[26:] # 'Out of memory'
print(timestamp, severity, message)
# Output: 2024-01-01 10:02:00 ERROR Out of memory
Using slicing on tuples
coordinates = (10, 20, 30, 40, 50)
x, y, z = coordinates[:3] # (10, 20, 30)
reversed_coords = coordinates[::-1] # (50, 40, 30, 20, 10)
print(x, y, z) # Output: 10 20 30
print(reversed_coords) # Output: (50, 40, 30, 20, 10)
Compare options / when to choose what
| Operation | Syntax | Best Use Case | Returns |
|---|---|---|---|
| Single element | seq[i] |
Access one specific item | That item (value) |
| Slice without step | seq[i:j] |
Get a contiguous range | New sequence |
| Slice with step | seq[i:j:k] |
Skip elements or reverse | New sequence |
| Negative indices | seq[-1] or seq[-3:-1] |
Access from the end | Single item or slice |
- Choose indexing when you need exactly one element.
- Choose slicing when you need a subset (or a copy).
- Use negative indices to avoid calculating the length.
- Use step = -1 to reverse any sequence without loops.
Troubleshooting & edge cases
Off-by-one errors
Remember: stop is exclusive. fruits[1:3] gives indices 1 and 2, not 1, 2, and 3. This is a common mistake.
Index out of range
fruits = ['apple', 'banana', 'cherry']
# print(fruits[3]) # IndexError: list index out of range
Slicing, however, handles out-of-range gracefully — it just returns an empty sequence.
print(fruits[10:20]) # []
Slices create copies
For mutable sequences like lists, slicing returns a new list. Modifying the slice does not affect the original.
original = [1, 2, 3]
copy = original[:]
copy[0] = 99
print(original) # [1, 2, 3]
print(copy) # [99, 2, 3]
Step with negative start/stop
Be careful with negative indices and a negative step — it can be tricky. Visualize the sequence and test with small examples.
# Get the last three elements in reverse order
lst = [0, 1, 2, 3, 4, 5]
print(lst[-1:-4:-1]) # [5, 4, 3]
# Equivalent to: start at last index, go backwards, stop before index -4 (which is 2)
Empty slice from identical start/stop
identical = fruits[1:1] # []
This is valid but empty.
What you learned & what's next
You can now confidently use slicing and indexing on sequences — lists, strings, tuples — to access single elements or subsets with precise control. You understand:
- Zero-based indexing and negative indices.
- Slice syntax
[start:stop:step]and how each part works. - Practical applications like reversing, extracting subsets, and copying.
- Edge cases including out-of-range indices and the exclusive stop.
This skill is foundational for the next lesson in your Python path: Looping and iteration — where you'll combine slicing with loops to process data efficiently.
Now it's your turn to practice. Write a short script that takes a string, extracts the middle third (approx), and prints it reversed. Test it with "Hello, World!" → you should get "Wor" reversed? Actually, challenge yourself to build a mini function that does it automatically.
Practice recap
Write a function extract_middle(s: str) -> str that returns a new string containing the middle third of s. Then print that result reversed. For example, extract_middle("Hello, World!") should return "o, W"? Actually, adjust: given length 13, middle third ≈ indices 4–8 → "o, Wo" reversed "oW ,o". Test with a few strings and compare outputs. This cements your understanding of slicing offsets and reversal.
Common mistakes
- Forgetting that stop is exclusive —
lst[1:3]gives indices 1 and 2, not 1–3. - Using an index that is out of range (IndexError) — only happens with single element access, not slicing.
- Assuming slicing modifies the original list — it always creates a new sequence (except for views in numpy).
- Mixing negative step with confusing start/stop order — for
[::-1], start defaults to end, stop to beginning.
Variations
- Use
slice()built-in object:s = slice(2, 5)then apply withmy_list[s]for dynamic slicing. - For strings, slicing includes Unicode correctly, but be careful with grapheme clusters (use
reor external libs). - For bytes and bytearray, slicing works identically, returning bytes or bytearray respectively.
Real-world use cases
- Parse CSV log records: slice each line to extract timestamp, severity, and message fields by fixed positions.
- Implement a pagination system: slice a full list of results to return only the items for a given page.
- Process DNA sequences: slice a long string to extract gene subsequences and reverse-complement them for analysis.
Key takeaways
- Indexing (
seq[i]) accesses a single element; slicing (seq[i:j:k]) returns a new subsequence. - Python uses zero-based indexing; negative indices count from the end.
- The stop index in a slice is exclusive —
seq[0:3]gives elements at indices 0, 1, 2. - Slicing with
[::-1]reverses any sequence (list, string, tuple) without a loop. - Slicing creates a copy for mutable sequences; it’s safe but uses memory.
- Use slicing for safe, readable data subset extraction — avoid manual loop-based construction.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.