Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

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

Sponsored

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 at 1, and so on.
  • Slice: A contiguous subset of a sequence, specified by start:stop:step. The stop is exclusive — it goes up to, but does not include, that index.
  • Negative indexing: Index -1 refers to the last element, -2 to 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 0 is before the first element. This helps understand why lst[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 step moves forward.
  • A negative step moves backward (used to reverse).
  • Omitting start and stop with 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

  1. Use slice() built-in object: s = slice(2, 5) then apply with my_list[s] for dynamic slicing.
  2. For strings, slicing includes Unicode correctly, but be careful with grapheme clusters (use re or external libs).
  3. 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.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.