Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python  Module 5 – Built-in data structures: lists, dicts, sets and more

Lists: create, index, slice and modify

Create Python lists, read items by position and by slice, change them with append, extend, insert, pop and remove, and avoid the x = x.sort() bug.

  • Beginner
  • 25 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free: 25 minutes

What you will learn

  • Create lists and read items by index and by slice
  • Add, remove and replace items with list methods and slice assignment
  • Distinguish methods that return None from operations that return a new list
  • Choose between a list and a deque for a queue served from the front

Before you start

What you’ll learn · Before you start

What you will learn

  • Create lists and read items by index and by slice
  • Add, remove and replace items with list methods and slice assignment
  • Distinguish methods that return None from operations that return a new list
  • Choose between a list and a deque for a queue served from the front

Before you start

On this page

A class of 40 students hands in marks, a shopping cart gains and loses items, and a file arrives line by line. Each is an ordered group of values that changes while the program runs, and Python’s list holds such a group. You write one with square brackets, as in cart = ["rice", "tea"], read an item by its index: The position of an item in a sequence such as a string or a list. Python counts from 0 at the left and from -1 at the right, so s[0] is the first item and s[-1] the last. More in “Strings: literals, indexing and slicing”, take a run of items with a slice: A run of items taken from a sequence with s[start:stop:step]. It includes start, stops before stop, and shortens itself instead of raising an error when a position is past the end. More in “Strings: literals, indexing and slicing”, and add, remove or replace items. A list keeps its items in the order you put them, holds values of any type and is mutable: Able to change in place after it is made. Lists, dicts and sets are mutable: append() changes the list itself, and every name bound to that list sees the change.: one list object changes in place.

Below you can step through a cart while each method changes it, and watch the items after an insert or a removal move along. You will also catch the two habits behind many list bugs: expecting a method such as sort() to hand the list back, and changing the front of a long list as if it cost no more than the back.

Square brackets, list() and repetition make lists

Most lists start as square brackets with commas between the items. This program makes lists in five ways and asks a few questions of them:

Five ways to make a list Python · make_lists.py
empty = []
cart = ["rice", "lentils", "tea"]
mixed = [3, "three", 3.0, None, ["a", "list", "inside"]]
letters = list("chai")  # any iterable: here, a string's characters
evens = list(range(0, 10, 2))
zeros = [0] * 4  # repetition

print(len(empty), len(cart), len(mixed))
print(letters)
print(evens)
print(zeros)
print(cart + ["salt"])  # + builds a new list ...
print(cart)  # ... and leaves cart as it was
print("tea" in cart, "milk" in cart)

Output

0 3 5
['c', 'h', 'a', 'i']
[0, 2, 4, 6, 8]
[0, 0, 0, 0]
['rice', 'lentils', 'tea', 'salt']
['rice', 'lentils', 'tea']
True False

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 make_lists.py

[] is an empty list. list() makes a list from any iterable: A value a for loop can go through one item at a time, such as a string, a list, a tuple, a dict, a set or a range. A dict gives its keys. More in “for loops with range, enumerate and zip”: a string gives its characters and a range its numbers. [0] * 4 repeats the items of a list, and + joins two lists into a third one, which is why cart still has three items after cart + ["salt"]. Both build new lists and leave the lists they were given alone. len() counts the items, and in checks whether any item is equal to a value, comparing items one at a time from the front.

The list called mixed holds an integer, a string, a float, None and another list. Python allows that, but most real lists hold one kind of value, because code that loops over a list usually does the same thing to every item. Press Edit, change list("chai") to list("masala") and run the program again: letters now holds six one-letter strings.

Comma Separated List Converter Turn a column of values into a Python list literal to paste into your code.

Checkpoint

Check: Making a list

  1. Question 1 of 2 cart = ["rice", "tea"], and then the line cart + ["salt"] on its own. What is cart afterwards?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: ['rice', 'tea']

    a + b returns a new list holding the items of a and then those of b. Nothing changes cart, and since the new list is not stored anywhere, it is lost straight away.

  2. Question 2 of 2 What is len([0] * 3 + ["x", "y"])?

    Type a number.

    A hint

    Work out [0] * 3 first, then join the two lists.

    Show the answer: Question 2 of 2

    Answer: 5 items

    [0] * 3 is [0, 0, 0], three items, and + puts the two items of ["x", "y"] after them in a new list of five items.

Indexes count from both ends, and a slice stops before its stop

Every item has a position, its index, counted from 0. Negative indexes count from the end, so cart[-1] is the last item and cart[-2] the one before it, however long the list is. The diagram shows both numberings for a cart of five items, and one slice:

A five-item list with positions 0 to 4 from the front and -5 to -1 from the back; cart[1:3] makes a new list of items 1 and 2.carta new list'rice' [0][-5]'lentils' [1][-4]'tea' [2][-3]'salt' [3][-2]'oil' [4][-1]'lentils' [0]'tea' [1]cart[1:3]: from position 1up to, not including, 3

Positions from the front and from the back, and one slice

Text description of the diagram

The diagram shows the list cart = ['rice', 'lentils', 'tea', 'salt', 'oil'] as a row of five cells, with a second, smaller list below it.

  1. Counted from the front, the positions are 0 to 4: cart[0] is 'rice' and cart[4] is 'oil'.
  2. Counted from the back, the same items are at -5 to -1: cart[-1] is 'oil', the last item, and cart[-5] is 'rice'.
  3. An arrow labelled cart[1:3] leads from the list to a new list holding 'lentils' and 'tea'. The slice starts at position 1 and stops before position 3, so 'salt' is not included, and in the new list the two items are at positions 0 and 1.

A slice takes a run of items: cart[start:stop] starts at start and stops just before stop, the same rule as range(). Leave out start to begin at the front and stop to run to the end, and add a third number, the step, to take every second or third item. A negative step walks backwards, which makes cart[::-1] a reversed copy:

Indexes and slices Python · positions.py
cart = ["rice", "lentils", "tea", "salt", "oil"]

print(cart[0], cart[2], cart[-1], cart[-2])
print(cart[1:3])  # positions 1 and 2: the stop is not included
print(cart[:2])  # from the start
print(cart[3:])  # to the end
print(cart[-2:])  # the last two items
print(cart[::2])  # every second item
print(cart[::-1])  # a reversed copy
print(cart[3:99])  # a slice that runs past the end is just shorter
print(cart[9])  # one position past the end is an error

Output (exit status 1)

rice tea oil salt
['lentils', 'tea']
['rice', 'lentils']
['salt', 'oil']
['salt', 'oil']
['rice', 'tea', 'oil']
['oil', 'salt', 'tea', 'lentils', 'rice']
['salt', 'oil']

Printed as an error (standard error)

Traceback (most recent call last):
  File "positions.py", line 11, in <module>
    print(cart[9])  # one position past the end is an error
          ~~~~^^^
IndexError: list index out of range

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 positions.py

Slices and indexes treat the edges differently. A slice that runs past the end is cut short without complaint, so cart[3:99] simply gives the last two items, and a slice that starts past the end is empty. A single index must point at an item that exists, and cart[9] stops the program with IndexError: list index out of range. A slice is also always a new list: changing it does not change cart. Press Edit, change the last line to print(cart[4]) and run it again: position 4 exists, so the program now ends normally, after printing oil.

Reaching for the last item with len()

cart[len(cart)] is one place past the end, because positions stop at len(cart) - 1, so it raises IndexError. Write cart[-1] for the last item: it works for a list of any length except an empty one.

Checkpoint

Check: Indexes and slices

  1. Question 1 of 2 nums = [10, 20, 30, 40, 50]. What is nums[1:4]?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: [20, 30, 40]

    nums[1:4] takes positions 1, 2 and 3: 20, 30 and 40. It stops before position 4, the same rule as range(1, 4).

  2. Question 2 of 2 cart has 5 items. Which of these raises IndexError?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: cart[5]

    Only a single index must point at an item that exists. Slices shorten themselves at the edges, so cart[3:99] and cart[5:] work, and cart[-5] is the first item.

append(), insert(), remove() and pop() change the list itself

The methods of a list change that very list instead of making a new one. Step through one cart as seven changes are made to it. The lit line has just run; watch the items after insert() and remove() move along:

Interactive · line by line One cart, seven changes Step through the program. The lit line has just run; watch the items after insert() and remove() move along, and each pop() hand back what it takes.

Program · cart_steps.py

cart = ["rice", "lentils"]
cart.append("tea")
cart.extend(["salt", "oil"])
cart.insert(1, "flour")
cart[0] = "brown rice"
cart.remove("lentils")
last = cart.pop()
second = cart.pop(1)
print(cart, last, second)

Variables

After line 9: cart is ['brown rice', 'tea', 'salt']; last is 'oil'; second is 'flour'.cart'brown rice'0'tea'1'salt'2last'oil'second'flour'
  • changed in this step

Printed so far

['brown rice', 'tea', 'salt'] oil flour

Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.

The drawing shows the variables when the program ends, and the program's 10 steps are listed below.

Line run 9
Next line end
Every step as text
  1. Start. No line has run yet, so there are no variables. Python starts with line 1.
  2. Line 1. cart = ["rice", "lentils"] makes cart a list of 2 items.
  3. Line 2. cart.append("tea") adds 'tea' to the end of cart.
  4. Line 3. cart.extend(["salt", "oil"]) adds 2 items to cart: it is now ['rice', 'lentils', 'tea', 'salt', 'oil'].
  5. Line 4. cart.insert(1, "flour") puts 'flour' at position 1 of cart: the items after it move along one place. Key point The new item goes before position 1, so 'lentils' and the three items after it each moved one place to the right.
  6. Line 5. cart[0] = "brown rice" changes cart: it is now ['brown rice', 'flour', 'lentils', 'tea', 'salt', 'oil'].
  7. Line 6. cart.remove("lentils") takes 'lentils' out of cart, from position 2: the 3 items after it move up one place.
  8. Line 7. last = cart.pop() sets last to 'oil'. pop() took 'oil' off the end of cart and handed it back, so last holds it. Nothing else had to move.
  9. Line 8. second = cart.pop(1) sets second to 'flour'. pop(1) took the item at position 1, 'flour', and handed it back; the two items after it moved up one place.
  10. Line 9. print(cart, last, second) prints ['brown rice', 'tea', 'salt'] oil flour. That was the last line, so the program ends.

insert(1, "flour") put the new item before position 1, and the four items after it each moved one place to the right. remove("lentils") deleted the first item equal to its argument, and the three items after it moved back. Each pop() took an item out and handed it back, which is how last and second got their values. The full set:

  • append(x) adds one item at the end, and extend(items) adds every item of an iterable, one by one.
  • insert(i, x) puts x before position i, and every later item moves one place to the right.
  • cart[i] = x replaces the item at a position.
  • remove(x) deletes the first item equal to x; pop() removes the last item and returns it, and pop(i) does the same for position i. del cart[i] deletes by position without returning anything, and clear() empties the whole list.
  • index(x) gives the position of the first item equal to x, and count(x) says how many items are equal to it.

The next program makes the same changes, prints the cart after each one, and ends with two classic surprises:

A shopping cart, one change at a time Python · cart.py
cart = ["rice", "lentils"]
print("start:          ", cart)

cart.append("tea")
print("append('tea'):  ", cart)

cart.extend(["salt", "oil"])
print("extend([...]):  ", cart)

cart.insert(1, "flour")
print("insert(1, ...): ", cart)

cart[0] = "brown rice"
print("cart[0] = ...:  ", cart)

cart.remove("lentils")
print("remove(...):    ", cart)

last = cart.pop()
print("pop():          ", cart, "gave", repr(last))

second = cart.pop(1)
print("pop(1):         ", cart, "gave", repr(second))

print("index('salt'):  ", cart.index("salt"))
print("count('tea'):   ", cart.count("tea"))

cart.append(["soap", "comb"])  # one new item: a whole list
cart.extend("ink")  # three new items: 'i', 'n' and 'k'
print("surprises:      ", cart)
print("length:         ", len(cart))

Output

start:           ['rice', 'lentils']
append('tea'):   ['rice', 'lentils', 'tea']
extend([...]):   ['rice', 'lentils', 'tea', 'salt', 'oil']
insert(1, ...):  ['rice', 'flour', 'lentils', 'tea', 'salt', 'oil']
cart[0] = ...:   ['brown rice', 'flour', 'lentils', 'tea', 'salt', 'oil']
remove(...):     ['brown rice', 'flour', 'tea', 'salt', 'oil']
pop():           ['brown rice', 'flour', 'tea', 'salt'] gave 'oil'
pop(1):          ['brown rice', 'tea', 'salt'] gave 'flour'
index('salt'):   2
count('tea'):    1
surprises:       ['brown rice', 'tea', 'salt', ['soap', 'comb'], 'i', 'n', 'k']
length:          7

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 cart.py

append() adds its argument as a single item, so appending a list nests it: ['soap', 'comb'] is one item, and the cart’s length counts it once. extend() loops over its argument, so extending by the string "ink" adds three one-letter strings. cart += "ink" behaves like extend() and adds the three letters too, while cart.append("ink") and cart += ["ink"] add the whole word as one item.

append() when you meant extend()

cart.append(["soap", "comb"]) adds one item, a list, so len(cart) grows by 1 and cart[-1] is the whole inner list. To add the two names as two items, write cart.extend(["soap", "comb"]) or cart += ["soap", "comb"].

When the item is not there

Two of the methods need the item to be in the list. This program asks for one that is not:

index() and remove() need the item Python · missing_item.py
cart = ["rice", "tea"]

if "milk" in cart:  # test first ...
    cart.remove("milk")
print("after the check:", cart)

print(cart.index("milk"))  # ... or index() and remove() raise ValueError

Output (exit status 1)

after the check: ['rice', 'tea']

Printed as an error (standard error)

Traceback (most recent call last):
  File "missing_item.py", line 7, in <module>
    print(cart.index("milk"))  # ... or index() and remove() raise ValueError
          ~~~~~~~~~~^^^^^^^^
ValueError: list.index(x): x not in list

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 missing_item.py

index() and remove() raise ValueError when no item is equal to the value, and pop() on an empty list raises IndexError. When an item may be missing, test with in first, as the first lines do. The lesson on exceptions shows the other way: catch the error.

Checkpoint

Check: Methods that change a list

  1. Question 1 of 2 row = ["a", "b", "c"], then row.insert(1, "x"). At which position is "c" now?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 3

    row.insert(1, "x") gives ['a', 'x', 'b', 'c']: the new item takes position 1, and every item that was at position 1 or later moves one place to the right.

  2. Question 2 of 2 What does this print?

    Read the code, then choose one answer.

    tags = ["new"]
    tags.append(["sale", "gift"])
    tags.extend("ok")
    print(len(tags))
    Show the answer: Question 2 of 2

    Answer: 4

    append() adds its argument as one item, so the inner list counts once. extend() loops over its argument, and a string gives its characters, so 'o' and 'k' are two more items: 4 in all.

Assigning to a slice replaces a whole range

A slice can also stand on the left of =, which is called slice assignment: An assignment with a slice on its left, as in week[1:3] = ['holiday']: the selected items are replaced by the new ones, which may be more or fewer unless the slice has a step.. The items of the slice are replaced by the items on the right, and the two counts do not have to match, so a list can grow or shrink in one statement:

Slice assignment and del Python · slice_assign.py
week = ["mon", "tue", "wed", "thu", "fri"]

week[1:3] = ["holiday"]  # two items replaced by one
print(week)

week[1:1] = ["tue", "wed"]  # an empty slice: insert, replace nothing
print(week)

del week[::2]  # delete positions 0, 2 and 4
print(week)

nums = list(range(10))
nums[::3] = ["x", "x", "x", "x"]  # an extended slice takes exactly as many items
print(nums)

Output

['mon', 'holiday', 'thu', 'fri']
['mon', 'tue', 'wed', 'holiday', 'thu', 'fri']
['tue', 'holiday', 'fri']
['x', 1, 2, 'x', 4, 5, 'x', 7, 8, 'x']

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 slice_assign.py

Two weekdays became one holiday, and the list got shorter. The empty slice week[1:1] is the gap before position 1: assigning to it inserts items and replaces none. del with a slice removes a range, and with a step, every second or third item. Press Edit, change the first assignment to week[1:3] = [] and run it: two items go and nothing takes their place, the same as del week[1:3].

Slices with a step other than 1 are stricter when you assign to them: they must get exactly as many items as they select, and nums[::3] = [1, 2] raises ValueError: attempt to assign sequence of size 2 to extended slice of size 4.

Checkpoint

Check: Assigning to a slice

  1. Question 1 of 2 nums = [1, 2, 3, 4, 5], then nums[1:4] = [0]. What is len(nums) now?

    Type a number.

    A hint

    Which items does nums[1:4] select, and how many take their place?

    Show the answer: Question 1 of 2

    Answer: 3 items

    nums[1:4] selects 2, 3 and 4, and all three are replaced by the one item 0, so the list becomes [1, 0, 5], three items.

  2. Question 2 of 2 nums = list(range(6)), then nums[::2] = [9, 9]. What happens?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: ValueError, because the slice selects 3 items and gets 2

    nums[::2] selects positions 0, 2 and 4 of [0, 1, 2, 3, 4, 5]. An extended slice is replaced one for one, so two items for three positions raises ValueError: attempt to assign sequence of size 2 to extended slice of size 3, and nums stays as it was.

Methods that change a list return None, except pop()

sort(), reverse(), append(), extend(), insert(), remove() and clear() all change the list and return None. That is a deliberate convention in Python: a method that changes an object in place: Changing an object itself instead of making a new one. list.sort() sorts a list in place and returns None, while sorted() leaves its input as it was and returns a new list. returns None unless it hands back an item, as pop() does, so that nobody mistakes it for one that makes a new object. Forget it, and the list is gone. Step through both halves of this program and watch the rows of queue and names:

Interactive · line by line The x = x.sort() bug, line by line Step through the program and watch the rows of queue and names. sort() puts each list in order, and what it hands back is None.

Program · sort_returns_none.py

queue = ["Ravi", "Asha", "Mei"]
result = queue.sort()
print(result)
print(queue)

names = ["Ravi", "Asha", "Mei"]
names = names.sort()
print(names)
names.append("Kiran")

Variables

After line 9: queue is ['Asha', 'Mei', 'Ravi']; result is None; names is None.queue'Asha'0'Mei'1'Ravi'2resultNonenamesNone
  • changed in this step

Printed so far

None
['Asha', 'Mei', 'Ravi']
None
Error AttributeError: 'NoneType' object has no attribute 'append'

Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.

The drawing shows the variables when the program ends, and the program's 9 steps are listed below.

Line run 9
Next line end
Every step as text
  1. Start. No line has run yet, so there are no variables. Python starts with line 1.
  2. Line 1. queue = ["Ravi", "Asha", "Mei"] makes queue a list of 3 items.
  3. Line 2. result = queue.sort() sets result to None. sort() put queue in order in place, as its row shows. What it returned, and result now holds, is None.
  4. Line 3. print(result) prints None.
  5. Line 4. print(queue) prints ['Asha', 'Mei', 'Ravi'].
  6. Line 6. names = ["Ravi", "Asha", "Mei"] makes names a list of 3 items.
  7. Line 7. names = names.sort() sets names to None (it was ['Ravi', 'Asha', 'Mei']). The bug The list was sorted first, and then the name names was bound to the None that sort() returned. Nothing refers to the sorted list any more.
  8. Line 8. print(names) prints None.
  9. Line 9. names.append("Kiran") stops the program with AttributeError: 'NoneType' object has no attribute 'append'. The error is reported on line 9, two lines after its cause on line 7.

names = names.sort() sorts the list and then binds names to the None that sort() returned. Nothing fails on that line. The error comes later, wherever the code next uses the list, and says 'NoneType' object has no attribute 'append', far from its cause. When you want a sorted copy, call sorted(names), which returns a new list; the lesson on sorting covers both in depth. The table pairs each change with the way to get a new list instead:

Change the list xs, or make a new one?
Criterion In placeNew list
Sort xs.sort()sorted(xs)
Reverse xs.reverse()xs[::-1]
Add one xs.append(x)xs + [x]
Add many xs += morexs + more
Remove xs.remove(x)build a new list
Returns Nonethe new list
When to choose Old order not neededOriginal still in use

reversed(xs) is the odd one out: it returns neither None nor a list, but an iterator that reads the list from the end, ready for a for loop, and list(reversed(xs)) turns it into a list.

Key idea

A method that changes a list returns None, pop() aside, so call it on a line of its own and keep using the list’s name, or call a function such as sorted() when you want a new list as the result.

Checkpoint

Check: In place or a new list

  1. One question What does this print?

    Read the code, then choose one answer.

    marks = [62, 40, 75]
    marks = marks.sort()
    print(marks)
    Show the answer: One question

    Answer: None

    marks.sort() sorts the list and returns None, and marks = … binds the name to that None. Write marks.sort() on a line of its own, or marks = sorted(marks).

Changing the front of a list moves every other item

The Python documentation describes how CPython stores a list: as an array of references to the item objects, with some spare room at the end. Adding or removing at the end touches one slot of that array. Inserting or removing at the front moves every other reference one place. Step through five tickets served from both ends, and count what moves:

Interactive · line by line Five tickets served from both ends Step through the program and count the items that move on each line. The end of a list is cheap to change; the front is not.

Program · tickets.py

tickets = [101, 102, 103, 104, 105]
tickets.pop()
tickets.pop(0)
tickets.insert(0, 100)
tickets.append(106)
print(tickets)

Variables

After line 6: tickets is [100, 102, 103, 104, 106].tickets10001021103210431064
  • changed in this step

Printed so far

[100, 102, 103, 104, 106]

Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.

The drawing shows the variables when the program ends, and the program's 7 steps are listed below.

Line run 6
Next line end
Every step as text
  1. Start. No line has run yet, so there are no variables. Python starts with line 1.
  2. Line 1. tickets = [101, 102, 103, 104, 105] makes tickets a list of 5 items.
  3. Line 2. tickets.pop() takes 105 off the end of tickets. Taking the last ticket moves nothing else.
  4. Line 3. tickets.pop(0) takes 101 out of tickets, from position 0: the 3 items after it move up one place. Key point Taking the first ticket moves every ticket behind it up one place. On a list of 40,000 items, one pop(0) moves 39,999 references.
  5. Line 4. tickets.insert(0, 100) puts 100 at position 0 of tickets: the items after it move along one place. Inserting at the front moves every ticket one place along again, to make room at position 0.
  6. Line 5. tickets.append(106) adds 106 to the end of tickets.
  7. Line 6. print(tickets) prints [100, 102, 103, 104, 106]. That was the last line, so the program ends.

pop() took 105 and nothing else moved. pop(0) took 101, and the three tickets behind it each moved up one place; insert(0, 100) then moved all three one place along again. On five items that costs nothing worth measuring, but the moves grow with the length of the list. This program empties lists of 10,000, 20,000 and 40,000 items from each end and times it:

Emptying a list from the back and from the front Python · front_vs_back.py
from collections import deque
from time import perf_counter


def best_ms(empty, make, n):
    """The fastest of three runs that empty a fresh n-item container, in ms."""
    times = []
    for _ in range(3):
        items = make(range(n))
        start = perf_counter()
        empty(items)
        times.append(perf_counter() - start)
    return min(times) * 1000


def from_back(items):
    while items:
        items.pop()


def from_front(items):
    while items:
        items.pop(0)


def from_deque(items):
    while items:
        items.popleft()


for n in (10_000, 20_000, 40_000):
    back = best_ms(from_back, list, n)
    front = best_ms(from_front, list, n)
    left = best_ms(from_deque, deque, n)
    print(f"{n:>6,} items  pop(): {back:5.1f} ms  pop(0): {front:6.1f} ms  deque.popleft(): {left:4.1f} ms")

Output

10,000 items  pop():   0.2 ms  pop(0):    7.0 ms  deque.popleft():  0.2 ms
20,000 items  pop():   0.4 ms  pop(0):   34.1 ms  deque.popleft():  0.3 ms
40,000 items  pop():   0.7 ms  pop(0):  148.0 ms  deque.popleft():  0.7 ms

This output changes from run to run: the times depend on the computer and on what else it is doing

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 front_vs_back.py

pop() emptied each list in about a millisecond or less. pop(0) needed dozens of times longer for 10,000 items and hundreds of times longer for 40,000, and each doubling of the list made it four to five times slower. The reason is the moving: emptying a list of n items from the front moves about n × n ÷ 2 references, some 800 million for 40,000 items, while pop() moves none. insert(0, x) has the same problem in the other direction.

So a list is the right tool for a A collection where the last item added is the first one taken out, like a pile of plates. A list makes a good one: append() adds at the end and pop() takes from the same end., where you add and remove at the end, and the wrong one for a A collection served in arrival order: the first item added is the first one taken out, like people waiting at a counter. served from the front. For a queue, use collections.deque, which adds and removes at both ends in constant time; the lesson on the collections module shows it in use.

Checkpoint

Check: The cost of the front

  1. Question 1 of 2 A list holds 1,000 items. How many of them move one place when pop(0) runs once?

    Type a number.

    A hint

    Every item behind the first one moves up one place.

    Show the answer: Question 1 of 2

    Answer: 999 items

    pop(0) takes the item at position 0, and each of the other 999 references moves one place towards the front. pop() at the end moves none.

  2. Question 2 of 2 Which of these get slower as the list jobs grows longer?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • jobs.pop(0)
    • jobs.insert(0, job)

    CPython stores a list as an array of references. Changing its front moves every other reference, so pop(0) and insert(0, x) cost more as the list grows, while append() and pop() work at the end and stay fast.

Practise: rotate a list and drop repeats

The exercise asks for two functions that return new lists and leave the list they are given unchanged: rotate() moves the items round with two slices, and dedupe_keep_order() keeps the first copy of each item. Its sample tests run in your browser, and its first hint shows where to start:

Exercise · Easy · Python

Rotate a list and drop repeated items, keeping the order

Write two functions that return new lists and leave the list they are given unchanged.

  • rotate(xs, k) moves every item k places to the right, and the items that fall off the end come back at the front: rotate([1, 2, 3, 4, 5], 2) is [4, 5, 1, 2, 3]. A negative k rotates to the left, so rotate([1, 2, 3, 4, 5], -1) is [2, 3, 4, 5, 1], and k may be larger than the list: rotating five items by 7 is the same as rotating them by 2. An empty list gives an empty list, whatever k is.
  • dedupe_keep_order(xs) keeps the first copy of every item and drops the later ones, without changing the order: dedupe_keep_order([3, 1, 3, 2, 1]) is [3, 1, 2].

Even when nothing changes, such as rotate(xs, 0), return a new list, not xs itself (a sample test checks with is not).

Starter code · listtools.py

def rotate(xs, k):
    """Return a new list: xs rotated k places to the right (negative k: to the left)."""
    return xs


def dedupe_keep_order(xs):
    """Return a new list with the first copy of each item, in their original order."""
    return xs
The sample tests · test_listtools.py
from listtools import dedupe_keep_order, rotate


def test_rotate_right():
    """rotates to the right"""
    assert rotate([1, 2, 3, 4, 5], 2) == [4, 5, 1, 2, 3]
    assert rotate(["a", "b", "c"], 1) == ["c", "a", "b"]


def test_rotate_left_and_large():
    """rotates to the left for a negative k, and wraps a k larger than the list"""
    assert rotate([1, 2, 3, 4, 5], -1) == [2, 3, 4, 5, 1]
    assert rotate([1, 2, 3, 4, 5], 7) == [4, 5, 1, 2, 3]
    assert rotate([1, 2, 3, 4, 5], -12) == [3, 4, 5, 1, 2]


def test_rotate_edges():
    """returns a new list for k = 0 and an empty list for an empty list"""
    xs = [1, 2, 3]
    same = rotate(xs, 0)
    assert same == [1, 2, 3]
    assert same is not xs
    assert rotate([], 3) == []
    assert rotate([9], 4) == [9]


def test_rotate_keeps_input():
    """leaves the list it is given unchanged"""
    xs = [1, 2, 3, 4]
    rotate(xs, 1)
    assert xs == [1, 2, 3, 4]


def test_dedupe():
    """keeps the first copy of each item, in order"""
    assert dedupe_keep_order([3, 1, 3, 2, 1]) == [3, 1, 2]
    assert dedupe_keep_order(["tea", "rice", "tea"]) == ["tea", "rice"]
    assert dedupe_keep_order([7, 7, 7]) == [7]


def test_dedupe_edges():
    """returns a new list, also for an empty list or a list without repeats"""
    xs = [1, 2, 3]
    result = dedupe_keep_order(xs)
    assert result == [1, 2, 3]
    assert result is not xs
    assert dedupe_keep_order([]) == []


def test_dedupe_keeps_input():
    """leaves the list it is given unchanged"""
    xs = [2, 2, 1]
    dedupe_keep_order(xs)
    assert xs == [2, 2, 1]
A hint

For rotate, first reduce k with k % len(xs), which gives a number from 0 to len(xs) - 1 for any k, also a negative one (check for an empty list before you divide by its length). Rotating right by k puts the last k items first: two slices joined with +. For dedupe_keep_order, build a new list in a loop and append an item only when it is not in the new list yet.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Python Online Compiler Try the methods of this lesson on a list of your own, in your browser.

Then work through the lesson’s self-check. It begins with the idea your answers show you know least, and each choice explains itself the moment you check it:

Practice that adapts to you

Practise this lesson

13 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 13 What does slice_assign.py print?

    What does this program print? Choose one answer.

    week = ["mon", "tue", "wed", "thu", "fri"]
    
    week[1:3] = ["holiday"]  # two items replaced by one
    print(week)
    
    week[1:1] = ["tue", "wed"]  # an empty slice: insert, replace nothing
    print(week)
    
    del week[::2]  # delete positions 0, 2 and 4
    print(week)
    
    nums = list(range(10))
    nums[::3] = ["x", "x", "x", "x"]  # an extended slice takes exactly as many items
    print(nums)
    Show the answer: Question 1 of 13

    Answer: it prints

    ['mon', 'holiday', 'thu', 'fri']
    ['mon', 'tue', 'wed', 'holiday', 'thu', 'fri']
    ['tue', 'holiday', 'fri']
    ['x', 1, 2, 'x', 4, 5, 'x', 7, 8, 'x']

    week[1:3] selects 'tue' and 'wed', and both are replaced by the one item 'holiday', so the list gets shorter. The empty slice week[1:1] inserts 'tue' and 'wed' before position 1 without replacing anything. del week[::2] deletes positions 0, 2 and 4 of the six-item list ('mon', 'wed' and 'thu'). nums[::3] selects positions 0, 3, 6 and 9, so its four items are replaced one for one.

  2. Question 2 of 13 What does this print?

    Read the code, then choose one answer.

    week = ["mon", "tue"]
    week[1:1] = ["x"]
    print(week)
    Show the answer: Question 2 of 13

    Answer: ['mon', 'x', 'tue']

    An empty slice such as week[1:1] selects no items, so assigning to it only inserts. Here 'x' goes in before position 1, as week.insert(1, "x") would put it.

  3. Question 3 of 13 What does this print?

    Read the code, then choose one answer.

    bag = ["pen", "ink"]
    bag.append(["clip", "tape"])
    print(len(bag), bag[-1])
    Show the answer: Question 3 of 13

    Answer: 3 ['clip', 'tape']

    append() adds its argument as one item, so the whole list ['clip', 'tape'] becomes the third item and bag[-1] is that inner list. bag.extend(["clip", "tape"]) would have added two items and given 4 tape.

  4. Question 4 of 13 What does this print?

    Read the code, then choose one answer.

    stack = [3, 7]
    top = stack.pop()
    print(top, stack)
    Show the answer: Question 4 of 13

    Answer: 7 [3]

    pop() takes the last item out of the list and hands it back, which is why a list makes a good stack: append() adds at the end and pop() takes from the same end.

  5. Question 5 of 13 A list may or may not contain "milk". Which code removes it when it is there and does nothing otherwise?

    Choose one answer.

    Show the answer: Question 5 of 13

    Answer: if "milk" in cart: followed by cart.remove("milk")

    remove() and index() need the item to be there, and pop() works by position. Test with in first, or catch the ValueError, which the lesson on exceptions shows.

  6. Question 6 of 13 nums = [5, 10, 15, 20, 25, 30]. What is nums[-2] + nums[1]?

    Type a number.

    A hint

    nums[-2] counts back from the end; nums[1] is the second item, because positions start at 0.

    Show the answer: Question 6 of 13

    Answer: 35

    nums[-2] is the second item from the end, 25, and nums[1] is the second item from the front, 10, because positions start at 0. 25 + 10 is 35.

  7. Question 7 of 13 What does this print?

    Read the code, then choose one answer.

    print([1, 2, 3, 4][::-1])
    Show the answer: Question 7 of 13

    Answer: [4, 3, 2, 1]

    Leaving out start and stop takes the whole list, and a step of -1 reads it from the back, so [::-1] is a reversed copy. The list itself is not changed.

  8. Question 8 of 13 What does this print?

    Read the code, then choose one answer.

    print(list("tea"))
    Show the answer: Question 8 of 13

    Answer: ['t', 'e', 'a']

    list(iterable) makes a list of the items the iterable gives. A string gives its characters, so list("tea") is ['t', 'e', 'a'], while ["tea"] is a list of one string.

  9. Question 9 of 13 What does this print?

    Read the code, then choose one answer.

    print(["a"] * 2 + ["b"])
    Show the answer: Question 9 of 13

    Answer: ['a', 'a', 'b']

    ["a"] * 2 is ['a', 'a'], a new list with the item repeated, and + builds another new list by putting ['b'] after it: ['a', 'a', 'b'].

  10. Question 10 of 13 What does this print?

    Read the code, then choose one answer.

    marks = [40, 75, 62]
    backwards = marks.reverse()
    print(backwards, marks)
    Show the answer: Question 10 of 13

    Answer: None [62, 75, 40]

    reverse() turns the list round in place and returns None, like the other methods that change a list, pop() aside. For a reversed copy that leaves marks alone, write marks[::-1].

  11. Question 11 of 13 Which of these leave the list items unchanged and give you a new list?

    Choose every answer that is right.

    Show the answer: Question 11 of 13

    Answer:

    • items + ["x"]
    • sorted(items)
    • items[:]

    sorted(), a slice and + all build new lists. sort(), extend() and insert() change items itself and return None.

  12. Question 12 of 13 A program serves 100,000 waiting requests in arrival order. Which choice keeps each step fast?

    Choose one answer.

    Show the answer: Question 12 of 13

    Answer: A collections.deque, adding with append() and serving with popleft()

    Changing the front of a list moves every other item, so pop(0), insert(0, x) and removing the first item all get slower as the list grows. A deque adds and removes at both ends in constant time.

  13. Question 13 of 13 Emptying a list of n items with pop(0) moves about n × n ÷ 2 references in all. About how many is that for 2,000 items?

    Type a number.

    A hint

    Multiply 2,000 by 2,000, then halve it.

    Show the answer: Question 13 of 13

    Answer: 2000000 references (anything from 1999000 to 2001000 counts)

    2,000 × 2,000 ÷ 2 is 2,000,000. The exact count is 1,999 + 1,998 + … + 1 = 1,999,000, so "about two million" either way, while emptying the same list with pop() moves none.

Interview questions

Fresher · What is the difference between append() and extend() on a Python list?

append(x) adds x as one new item at the end, whatever x is, so appending a list of three items makes the list one item longer, with the inner list nested inside it. extend(items) loops over its argument and adds each item on its own, so extending by a list of three items makes the list three items longer. Strings show the difference best: cart.extend("tea") adds the letters ‘t’, ‘e’ and ‘a’, because a string is a sequence of characters, while cart.append("tea") adds one string. Both change the list in place and return None, and += on a list works like extend(). To combine two lists without changing either, write a + b, which builds a new list.

Key takeaways

  • A list is ordered and mutable and can hold any values; brackets, list(iterable), [x] * n and a + b make one, and the last two build new lists from old ones.
  • Index 0 is the first item and -1 the last; a[start:stop:step] stops before stop, never raises IndexError and always returns a new list, while a single index past the end raises it.
  • append() adds one item and extend() each item of an iterable, insert() and remove() move the items after them, and pop() returns what it removes, while index() and remove() raise ValueError for a missing value.
  • Assigning to a slice replaces a whole range, and the sizes may differ unless the slice has a step other than 1.
  • Methods that change a list return None, pop() aside, so x = x.sort() throws the list away; sorted(x) returns a new list.
  • Changing the front of a list moves every other item, so a list makes a good stack and collections.deque a good queue.

Terms in this lesson

in place
Changing an object itself instead of making a new one. list.sort() sorts a list in place and returns None, while sorted() leaves its input as it was and returns a new list.
mutable
Able to change in place after it is made. Lists, dicts and sets are mutable: append() changes the list itself, and every name bound to that list sees the change.
slice assignment
An assignment with a slice on its left, as in week[1:3] = ['holiday']: the selected items are replaced by the new ones, which may be more or fewer unless the slice has a step.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.