Building and Modifying Lists

Introduction: Lists That Change

Welcome back to Building and Modifying Lists in Python. Now that we can already create lists, read items out of them, and slice them apart, our lists can do more than stay read-only: we can reshape them.

That changes now. Lists are mutable, which means they can be modified after they are created. Recall that strings were immutable: text.replace() handed us a brand-new string and left the original untouched. Lists work the other way around. When we add or remove an item, the list itself changes, and every variable pointing to that list sees the update.

Throughout this lesson, we will work with one small task list that starts with two tasks and gets reshaped step by step. Along the way, we will meet three ways to add items (append(), insert(), and extend()) and three ways to take them away (remove(), pop(), and del).

Two Questions Behind Every Change

Before touching any code, it helps to know what separates these six tools from one another. Almost every choice comes down to two questions.

  1. How many items are involved? append() and insert() handle exactly one item; extend() adds every item produced by an iterable we hand it.
  2. Do we know the value or the position? remove() searches for a value and deletes the first match. pop() and del work by position, so they need an index instead.

Keeping these two questions in mind turns a list of six method names into two short steps. Our starting point is deliberately tiny, so the effect of each operation stays easy to follow:

Chart for choosing between append, insert, extend, remove, pop, and del

With those choices in mind, start with this small task list:

Python
tasks = ["email", "report"]

Adding One Item to the End with append()

The most common way to grow a list is append(), which sticks a single item onto the end:

Python
tasks = ["email", "report"]

# append() adds one item to the end
tasks.append("call")
print(tasks)

Three details are worth locking in:

  • It adds exactly one item, whatever that item happens to be.
  • The new item always lands at the end, so the existing positions stay valid.
  • It changes the list in place and returns None. This is why tasks = tasks.append("call") is a classic beginner bug: it quietly replaces our list with None.

The list grew from two items to three, in the order we expect:

text
['email', 'report', 'call']

Placing an Item at a Position with insert()

When the end is not where we want the new item, insert() lets us pick the spot. It takes two arguments: the index the new item should occupy and the value itself.

Python
# insert() adds one item at a specific position
tasks.insert(0, "standup")
print(tasks)

Index 0 means "make this the first item," so "standup" moves to the front and everything else shifts one position to the right: "email" was at index 0 and is now at index 1. The list is one item longer than before.

text
['standup', 'email', 'report', 'call']

An index past the end is not an error; insert() simply appends in that case. Also worth knowing: inserting at the front means Python has to shift every other item over, so it takes a little more work than appending. That is intuition for later, not a rule to memorize.

Adding Many Items at Once with extend()

To add several items in one step, we reach for extend(). It accepts an iterable—a value Python can take items from—and adds each item it produces one by one, in order, onto the end of the list. A list is a common introductory example:

Python
# extend() adds every item from another iterable
tasks.extend(["lunch", "review"])
print("After adding:", tasks)

Our list now holds six items, all at the same level:

text
After adding: ['standup', 'email', 'report', 'call', 'lunch', 'review']

The contrast with append() is the part to remember. extend() takes the items from the iterable one at a time; append() would treat the entire iterable as one single value:

Python
a = ["email"]
a.extend(["lunch", "review"])   # adds two items
b = ["email"]
b.append(["lunch", "review"])   # adds one item, which is a list
print(a)
print(b)
text
['email', 'lunch', 'review']
['email', ['lunch', 'review']]

Lists are not the only values that work with extend(). For example, tasks.extend(("lunch", "review")) works with a tuple, and tasks.extend("go") adds "g" and "o" because strings are iterable too. In this course, we will usually use another list because it makes the added items easy to see.

Removing by Value with remove()

Now we start shrinking. Use remove() when we know what should disappear but not where it currently sits:

Python
# remove() deletes the first matching value
tasks.remove("lunch")
print(tasks)

Python scans the list from left to right, finds "lunch" at index 4, and takes it out; the items after it shift left to close the gap.

text
['standup', 'email', 'report', 'call', 'review']

Two behaviors deserve attention. First, only the first match goes away: if "lunch" had appeared twice, the second copy would still be in the list. Second, a value that is not in the list raises a ValueError. The in operator from the strings course is the natural guard here, since if "lunch" in tasks: lets us check before removing. And like the methods that add items, remove() changes the list in place and returns None, so there is nothing worth catching from it.

Removing by Position with pop()

Sometimes we want the removed item back so we can use it. That is exactly what pop() is for:

Python
# pop() removes by position and returns the removed item
done = tasks.pop()
print("Popped:", done)

Called with no argument, pop() removes the last item and hands it back, so done now holds the string "review" while tasks is one item shorter. This makes pop() ideal for "take the next item off the list and do something with it" patterns.

text
Popped: review

We can also pass an index: tasks.pop(0) removes and returns the first item, and the items after it shift left to close the gap, exactly as they do with remove(). On an empty list, pop() has nothing to give back and raises an IndexError, so an emptiness check is wise in loops.

Deleting by Position with del

When we know the position and do not care about the value, del is the most direct option:

Python
# del removes by position without returning the item
del tasks[0]
print("After removing:", tasks)

Notice the shape of this line: del is a statement, not a method. There is no dot, no parentheses, and no return value, so writing x = del tasks[0] is not valid Python at all. Here, it drops "standup" from index 0 and the later items shift left to close the gap, leaving the three original-flavored tasks behind:

text
After removing: ['email', 'report', 'call']

One bonus: del accepts slices, too. del tasks[0:2] would delete a whole range in a single statement, something neither remove() nor pop() can do.

Choosing the Right Tool

Here is the whole toolkit side by side:

ToolWhat it doesWorks byItems affectedReturns
append(x)Adds x at the endPosition (end)OneNone
insert(i, x)Adds x at index iPositionOneNone
extend(iterable)Adds each item from an iterablePosition (end)ManyNone
remove(x)Deletes first match of xValueOneNone
pop([i])Removes item at i (default last)PositionOneThe item
del list[i]Deletes item or slice at iPositionOne or manyNothing

The central split is by value (remove()) versus by position (pop() and del), paired with the one item versus many items split on the adding side. Put together, our full script reshapes one list six times:

Python
tasks = ["email", "report"]

tasks.append("call")                 # ['email', 'report', 'call']
tasks.insert(0, "standup")           # ['standup', 'email', 'report', 'call']
tasks.extend(["lunch", "review"])    # six items now
print("After adding:", tasks)

tasks.remove("lunch")                # by value
done = tasks.pop()                   # by position, value captured
print("Popped:", done)

del tasks[0]                         # by position, value discarded
print("After removing:", tasks)
text
After adding: ['standup', 'email', 'report', 'call', 'lunch', 'review']
Popped: review
After removing: ['email', 'report', 'call']

Conclusion and Next Steps

In this lesson, we made lists move. We grew them with append() for a single item at the end, insert() for a chosen position, and extend() for many items from an iterable. We shrank them with remove() by value, pop() by position with the value handed back, and del by position with the value discarded.

The idea tying all six together is mutability. Each of these operations edits the existing list rather than producing a copy, which is why we print the list afterward instead of assigning the result to a new variable. Only pop() gives us something worth catching, and what it gives us is the removed item, not the list.

Time to put this into practice: you will append and insert tasks, extend a list with another one, remove an item by value, and finish by popping and deleting by position. Once those feel natural, explore how to change items that are already in a list and how to put them in order.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal