← Home

054. Lists

Ordered mutable collections of arbitrary items

054. Lists

Aryan’s RAM monitor accumulates process records as it scans. He needs a list that grows, shrinks, and stays ordered by discovery time:

processes = []  # start empty

for proc in psutil.process_iter(['pid', 'name', 'memory_info']):
    try:
        rss_mb = proc.info['memory_info'].rss / 1e6
        processes.append({          # add to end — O(1)
            "name": proc.info['name'],
            "pid":  proc.info['pid'],
            "rss_mb": rss_mb,
        })
    except (psutil.NoSuchProcess, psutil.AccessDenied):
        pass

Then he pops the highest-memory process off a sorted copy to alert on it, without destroying the original list:

ranked = sorted(processes, key=lambda p: p["rss_mb"], reverse=True)
top = ranked[0]   # highest RSS — read only, no mutation
print(f"Top consumer: {top['name']} ({top['rss_mb']:.0f} MB)")

The trap he falls into first: processes + [new_proc] looks like it adds to the list, but it creates a new list and discards it. .append() is the mutating version — it modifies processes in place.


💡 Fun fact: Python lists are implemented as dynamic arrays — contiguous blocks of memory that double in capacity when full. This means append() is amortized O(1): most appends are instant, but occasionally Python allocates a new, larger array and copies everything over. This “doubling” strategy (used in C++’s std::vector and Java’s ArrayList too) was invented by computer scientist John L. Bentley and ensures that building a list of n items takes O(n) time total.

⚠️ Watch out: lst + [item] vs lst.append(item) is one of the most common beginner traps in Python. lst + [item] creates a new list and the original lst is unchanged — if you write lst + [item] without assigning the result, the new item is silently discarded. Always use .append() when you want to mutate the list in place.

🤔 Think about it: Lists are passed by reference in Python — add_song(playlist, song) modifies the caller’s list, not a copy. If you wanted add_song to return a new list without touching the original, how would you implement that? And why might a “pure” function that never mutates its inputs be preferable in some situations?

Learning objectives

  • Create lists and add/remove elements
  • Use append() for adding and pop() for removing
  • Access first/last elements with [0] and [-1]

Key concepts

  • list
  • append()
  • pop()
  • mutable

Try it

Concept detail

Lists are ordered, mutable, can hold any type, and can grow/shrink. Create: [], [1,2,3], list(“abc”) == [‘a’,‘b’,‘c’], list(range(5)).

Mutation vs. creation — this trips everyone up: lst.append(x) # mutates lst — adds x to the end, returns None lst + [x] # creates a NEW list — lst is unchanged lst.extend([x,y]) # mutates lst — adds multiple items lst += [x] # mutates lst in place (same as extend for lists)

Other common methods: pop() — remove and return last element pop(0) — remove and return first element (O(n) — slow for big lists) insert(i, x) — insert x at index i remove(x) — remove first occurrence of x

Lists are passed by reference — mutating inside a function affects the caller. Use lst.copy() to avoid mutating the caller’s list.

Solution

def create_playlist(songs):
    return list(songs)

def add_song(playlist, song):
    playlist.append(song)
    return playlist

def remove_first(playlist):
    return playlist.pop(0)

def playlist_info(playlist):
    return {"count": len(playlist), "first": playlist[0], "last": playlist[-1]}

Tests

def test_create_playlist():
    result = create_playlist(["song1", "song2"])
    assert type(result) == list
    assert result == ["song1", "song2"]

def test_add_song():
    pl = ["a", "b"]
    result = add_song(pl, "c")
    assert result == ["a", "b", "c"]
    assert pl == ["a", "b", "c"]  # mutated in place

def test_add_song_empty():
    pl = []
    add_song(pl, "x")
    assert pl == ["x"]

def test_remove_first():
    pl = ["a", "b", "c"]
    removed = remove_first(pl)
    assert removed == "a"
    assert pl == ["b", "c"]

def test_playlist_info():
    info = playlist_info(["rock", "jazz", "pop"])
    assert info["count"] == 3
    assert info["first"] == "rock"
    assert info["last"] == "pop"

Resources