Addaly is in open beta. Things will change, and AI answers can be wrong — check anything that matters.

Python, From Zero, For AI

From your first line of code to your first API call.

Lesson 12 of 898 min

Reaching into a sequence: indexes, slices and why the end is excluded

Counting starts at zero

python
items = ["rice", "dal", "oil", "salt"]
items[0]     # 'rice'
items[3]     # 'salt'
items[4]     # IndexError: list index out of range

Four items, valid positions 0 to 3. The last position is always len(items) - 1, and asking for len(items) itself is the single most common IndexError.

Negative numbers count from the right:

python
items[-1]    # 'salt'  — last
items[-2]    # 'oil'   — second from last

items[-1] is worth a habit. It means "the last one" without computing a length, and it keeps working when the list changes size.

A slice takes a range

python
items[1:3]     # ['dal', 'oil']
items[:2]      # ['rice', 'dal']    from the start
items[2:]      # ['oil', 'salt']    to the end
items[:]       # a full copy

The first number is where to start, the second is where to stop before. items[1:3] gives positions 1 and 2, not 1 to 3.

People find this arbitrary. It is not, and two properties explain why it was chosen:

  1. The length of a slice is stop - start. items[2:5] has 3 elements. No arithmetic, no off-by-one.
  2. items[:k] + items[k:] reconstructs the original, for any k. The cut point belongs to exactly one side. With an inclusive end you would need items[:k] + items[k+1:] and you would drop an element every time you forgot.

Once you have written a few loops that split a list at a moving point, the convention stops feeling odd.

The step, and reversing

A third number is the step:

python
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers[::2]     # [0, 2, 4, 6, 8]     every second
numbers[1::2]    # [1, 3, 5, 7, 9]     every second from position 1
numbers[::-1]    # [9, 8, 7, ..., 0]   reversed

[::-1] is the standard idiom for reversing a list or a string. It builds a new reversed copy; list.reverse() reverses in place and returns nothing.

The difference that saves you from crashes

Indexing out of range raises. Slicing out of range does not.

python
short = ["a", "b"]
short[5]        # IndexError
short[1:99]     # ['b']
short[10:20]    # []   — an empty list, no error

This is genuinely useful. Taking "the first ten results" with results[:10] is safe whether there are 3 results or 3,000. Writing results[0] on a possibly-empty list is not, and if results: has to guard it.

Given a search or an API that may return nothing, results[:1] gives you a list with zero or one item, and a loop over it does the right thing in both cases without an if.

Slicing strings works the same way

python
code = "ADD-2026-0147"
code[:3]      # 'ADD'
code[4:8]     # '2026'
code[-4:]     # '0147'

But strings are immutable — you cannot assign into one:

python
code[0] = "X"    # TypeError: 'str' object does not support item assignment

You build a new string instead: "X" + code[1:]. Lists do allow assignment, including to a whole slice:

python
items[1:3] = ["atta"]      # replaces two items with one; the list shrinks

That last form surprises people. Slice assignment does not have to be the same length.

A slice of a list is a shallow copy

python
a = [1, 2, 3]
b = a[:]
b.append(4)
a            # [1, 2, 3] — untouched

a[:] was the standard way to copy a list before list(a) and a.copy() existed. All three do the same thing, and all three are shallow: if the list contains other lists, both copies point at the same inner lists. The next lesson is about exactly that.

Slices on strings copy; slices on big data may not

For lists and strings, a slice allocates a new object, so big_list[:] on a million items costs a million pointer copies. Inside a loop that is a real cost people miss. NumPy arrays and pandas frames, which arrive later in this course, behave differently — a NumPy slice is a view onto the original memory, and writing to it changes the original. Two libraries, two rules; the course flags it again when you get there.

Try this now

Take "2026-09-04" and extract the year, the month and the day with slices. Then take a list of ten numbers and produce, with slices only: the first three, the last three, every alternate one, and the whole thing backwards. Four one-liners, no loops.

The one thing to keep

A slice stops before its second index, which makes its length exactly stop minus start, and slicing out of range returns a shorter result instead of raising.

Before you move on

A function returns the top three matches with `results[:3]`. A colleague argues this will crash when the search finds only one match and rewrites it as `results[0:3] if len(results) >= 3 else results`. What is true?

Pick the one you would defend. Nobody sees your answer.

No ads. No data sale. No public scores on people. Ever.

© 2026 Addaly

Reaching into a sequence: indexes, slices and why the end is excluded · Python, From Zero, For AI · Addaly