PythonBeginner 14 min Lesson 4 of 30

Day 4 — Lists

Lists are the workhorse of Python. Learn how to build them, change them, slice them, sort them, and avoid the copy trap that bites beginners.

Python · Lesson 4 of 30
0/30 done(0%)

What is it? #

A list holds several values in order, under one name. It keeps the order you put things in, allows duplicates, and can hold any mix of types — though in practice you almost always keep one kind of thing in one list.

Unlike strings, lists are mutable. You can append to them, remove from them and overwrite items in place. That is the main reason they are everywhere: most programs collect things as they go.

Lists are indexed from 0 and support the same slicing syntax as strings. Everything you learned yesterday about [start:stop] still applies.

The one sharp edge is copying. Assigning a list to a second name does not make a copy — both names point at the same list, so changing one changes "both". Knowing this saves hours of confusion later.

Think of it like this #

A list is a numbered shopping list on paper. You can add a line at the bottom, cross one out, or read line 3 directly. If you hand the same sheet to a friend and they add milk, your sheet has milk on it too — because there is only one sheet, not two.

Simple example #

You are collecting the items in a shopping cart. Items get added as the customer browses, one gets removed, and at checkout you want them sorted by name and you want the total count.

Code #

PYTHON
cart = ["keyboard", "mouse"]

cart.append("monitor")            # add to the end
cart.insert(0, "laptop")          # add at a position
print(cart)                       # ['laptop', 'keyboard', 'mouse', 'monitor']

cart.remove("mouse")              # remove by value
last = cart.pop()                 # remove and return the last item
print(cart, "| removed:", last)   # ['laptop', 'keyboard'] | removed: monitor

print(len(cart))                  # 2
print("laptop" in cart)           # True
print(cart[0], cart[-1])          # laptop keyboard

prices = [1299, 499, 2999, 899]
print(sorted(prices))             # [499, 899, 1299, 2999] — new list
prices.sort(reverse=True)         # sorts in place
print(prices)                     # [2999, 1299, 899, 499]

# The copy trap
a = [1, 2, 3]
b = a                             # same list, two names
b.append(4)
print(a)                          # [1, 2, 3, 4]

c = a.copy()                      # a real copy
c.append(5)
print(a, c)                       # [1, 2, 3, 4] [1, 2, 3, 4, 5]

How it works #

.append() adds one item to the end and returns nothing. That "returns nothing" part matters: cart = cart.append("x") throws your list away and leaves you with None.

.insert(0, "laptop") puts an item at a given position and shifts everything after it along. Inserting at the front is slower than appending, because every other item has to move.

.remove("mouse") finds the first matching value and deletes it; it raises an error if the value is not there. .pop() removes by position (the last one by default) and hands the item back, which is what makes a list usable as a stack.

sorted(prices) returns a new sorted list and leaves the original alone. prices.sort() sorts the list itself and returns None. Most confusion about sorting comes from mixing those two up.

The copy trap block is the important one. b = a copies the reference, not the contents — one list, two tags on it. a.copy() (or list(a), or a[:]) creates a genuinely separate list. Note that for lists of lists, this is still a shallow copy: the inner lists are shared, and you would need copy.deepcopy for full separation.

Real-world use #

Lists back almost every "collection of things" in an application: rows fetched from a database, files found in a folder, validation errors gathered while checking a form, items in a cart, messages waiting to be sent.

The mutability-and-sharing behaviour shows up in real bugs constantly. A function receives a list, appends to it for its own purposes, and the caller's list silently grows too. Once you know that lists are passed by reference, that class of bug becomes obvious instead of mysterious.

The performance side matters at scale: appending to the end is cheap, inserting or deleting at the front is not, and checking in on a long list scans the whole thing. When you need fast membership checks, a set or dictionary is the right tool — you will meet both on Day 5 and Day 6.

Common mistakes #

  • Writing cart = cart.append("x"). append returns None, so you lose the list.
  • Assuming b = a makes a copy. Use a.copy(), list(a) or a[:] when you need a separate list.
  • Mixing up sorted(x) (returns a new list) with x.sort() (sorts in place and returns None).
  • Removing items from a list while looping over it. The indexes shift and items get skipped — build a new list instead.
  • Using in on a huge list inside a loop. That is a scan every time; a set gives you a near-instant check.

Practice #

Start with tasks = ["deploy", "review", "write tests"]. Append two more tasks, remove one by value, print how many are left, print them sorted alphabetically without changing the original order, and finally print the original list to prove it is unchanged.

Quick quiz

  1. 1. What does `list.append()` return?

  2. 2. After `b = a` where `a` is a list, what happens if you append to `b`?

  3. 3. Which returns a new sorted list without changing the original?

  4. 4. What does `items.pop()` do with no argument?

  5. 5. Why is removing items while iterating over a list risky?

Summary

  • Lists are ordered, mutable collections indexed from 0.
  • append, insert, remove and pop change the list in place and mostly return None.
  • `sorted(x)` gives you a new list; `x.sort()` rearranges the one you have.
  • Assignment shares a list between names — copy it explicitly when you need independence.
  • Appending at the end is cheap; inserting at the front and scanning with `in` are not.