Skip to main content

Lists

Level 1: Level 1: Foundationseasy10 minlistsindexingappendmutability

Build, index, slice, and grow Python's ordered, mutable collection.

Why lists are the default container

Reach for a list any time you have an ordered sequence you will read, grow, or reshape: rows streamed from a query, tokens parsed from a line, a batch of records waiting to be written. It is the collection you build up in a loop and hand off to the next stage of a pipeline. Because it is both ordered and changeable, one list can serve as your accumulator, your buffer, and your result all at once.

The mental model: a dynamic array of references

A Python list is a dynamic array. Under the hood it holds a contiguous block of slots pointing at your objects, and the interpreter resizes that block for you as the list grows. Two consequences follow. First, reaching any position by index is a direct jump, so nums[i] costs the same whether the list has 3 items or 3 million. Second, the list stores references, not copies, so the same object can sit in more than one list at once.

You write a list with square brackets and index it like a string, starting at 0:

nums = [10, 20, 30]
nums[0]      # 10          first item
nums[-1]     # 30          negative counts from the end
nums[1:]     # [20, 30]    a slice returns a new list
len(nums)    # 3           how many items

Mutability: changing in place

Unlike strings and tuples, lists are mutable. The methods below change the existing list rather than returning a new one:

nums.append(40)     # [10, 20, 30, 40]      add to the end
nums.insert(0, 5)   # [5, 10, 20, 30, 40]   add at an index
nums.remove(20)     # [5, 10, 30, 40]        remove the first 20

The demo below starts from [10, 20, 30], calls append(40), and prints [10, 20, 30, 40], so nums[-1] is 40 and len(nums) is 4. Notice append returns None: it mutates the list and hands nothing back, which is exactly why the Apply task asks you to append and then return items on a separate step.

Check yourself
You write nums = nums.sort() and then print(nums). What prints?

That split, mutate here and return there, applies to every list operation:

Table
Read the highlighted column before you assign. nums = nums.sort() is the classic beginner bug: sort works perfectly, returns None, and you overwrite your list with None. Use nums.sort() alone to reorder, or nums = sorted(nums) to rebind.
You callEffect on the original listWhat it hands back
lst.append(x)x is added to the endNone
lst.sort()lst is reordered in placeNone
lst.reverse()lst is reversed in placeNone
lst.pop()the last item is removedthe removed item
sorted(lst)untoucheda new sorted list
reversed(lst)untoucheda lazy iterator
lst + [x]untoucheda new list
Read the highlighted column before you assign. nums = nums.sort() is the classic beginner bug: sort works perfectly, returns None, and you overwrite your list with None. Use nums.sort() alone to reorder, or nums = sorted(nums) to rebind.
Check yourself
A helper is written as def add_zero(items): items.append(0) followed by return items. You call it with your list scores, then print scores. What do you see?

Pitfall: aliasing shares one object

Assignment copies the reference, not the list. Both names then point at the same object:

a = [1, 2, 3]
b = a
b.append(4)
print(a)        # [1, 2, 3, 4]   a changed too

If you wanted an independent copy, make one explicitly with a[:], list(a), or a.copy(). Interns lose hours to a helper that quietly mutates the caller's list.

For the Practice task, the middle index is len(items) // 2. Integer division // floors the result, so a 5-item list gives index 2, landing on the true center 30. On an even-length list it picks the right-of-center item, which is the intended, deterministic rule.

Check yourself
You drain a queue by calling items.pop(0) over and over until the list is empty. Why does that crawl on a large list?

Interview nuance: know the cost of each operation. Indexing and append are effectively O(1) (append is amortized O(1) because the backing array over-allocates), but insert(0, x), remove, and pop(0) are O(n) because every later element shifts one slot. If a problem needs fast inserts or removes at the front, that is the signal to reach for collections.deque instead of a list.

Worked example (Python)
nums = [10, 20, 30]
nums.append(40)
print(nums)        # [10, 20, 30, 40]
print(nums[-1])    # 40
print(len(nums))   # 4

Apply

Your turn

The task this lesson builds to.

Implement add_item(items, value): append value to the list items and return the list.

For ([1, 2], 3) return [1, 2, 3].

3 hints and 4 automated checks are waiting in the workspace.

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Implement middle_item(items): return the item at the middle index of the list.

The middle index is len(items) // 2. For [10, 20, 30, 40, 50] return 30.

2 hints and 4 automated checks are waiting in the workspace.