← Back to Core Python
Lesson 16 · Python Collections

List Methods and Operations

Combine, search, sort, and copy lists - and the methods that return None.

Beginner45 min

What you will be able to do

  • Combine lists with + and repeat them with *, and say which one builds a new list
  • Check membership with in and not in, and compare lists with == and <
  • Choose correctly between append, extend, and insert
  • Remove items with remove, pop, clear, and del, and say what each one gives back
  • Sort in place with sort() or into a new list with sorted(), including a key function
  • Copy a list safely, and explain what a shallow copy does not copy
  • Recognise which list operations get slow as the list grows

The idea, in plain English

Lesson 15 gave you a list and showed you how to reach into it. This lesson is about changing it. Almost everything an application does to a collection is in here: add a course, drop an inactive user, sort products by price, merge two pages of API results, check whether something is already present.

The tools come in two shapes. Operators - +, *, in, ==, < - are symbols that work on lists the way they work on numbers. Methods - append, extend, sort, copy - are functions attached to the list itself, called with a dot. The split matters because they tend to behave differently: operators mostly build something new, methods mostly change the list you already have.

That difference is the single biggest source of confusion in this lesson. `a + b` leaves a alone and hands you a third list. `a.extend(b)` changes a and hands you nothing. Both end with the same elements in the same order, and which one you want depends entirely on whether anyone else is holding a reference to a.

Three things here catch people out every time, so watch for them: methods that change the list return None rather than the list, extend() takes an iterable so a string gets split into characters, and assigning a list to a second name does not copy it. Each one has bitten every Python developer at least once.

Worked example: Ranking students by score, then by name when two scores tie.

append adds one object, extend adds each element

append(x) means "put x on the end as a single item". Whatever x is - a number, a string, an entire list - it becomes one element. So [1, 2, 3].append([4, 5]) gives [1, 2, 3, [4, 5]], a list of four things, the last of which is itself a list.

extend(iterable) means "walk through this and add each element you find". The same call with extend gives [1, 2, 3, 4, 5], a list of five things. This is the one you want when you are merging.

The surprise is that a string is iterable. letters.extend("cd") adds "c" and then "d" as separate elements, not "cd" as one. If you wanted the whole string, you wanted append. Nothing errors, so this is a bug you find later, in the data.

insert(index, value) is append with a position. Give it an index past the end and it appends rather than failing, which is convenient but means a wrong index fails silently.

Adding to a list
items.append(x)Adds x as exactly one element, whatever type x is.
items.extend(xs)Adds each element of xs. Accepts any iterable - list, tuple, string, range.
items.insert(i, x)Adds x at position i and shifts the rest right. An i past the end just appends.
items + otherBuilds a new list. Both originals are untouched. Requires a list on both sides.
items * 3Builds a new list with the elements repeated three times. An empty list stays empty.
items += otherBehaves like extend - it changes the list in place, unlike items = items + other.

Watch out: extend("Python") adds six separate characters. If you meant one element, use append.

sort() changes the list, sorted() hands you a new one

numbers.sort() rearranges the list you called it on and returns None. sorted(numbers) leaves the original alone and returns a new, sorted list. The same split shows up again with reverse() against reversed().

Use sort() when you own the list and the old order is of no value. Use sorted() when the original order matters to someone else, or when you are sorting something that is not a list - sorted() accepts any iterable and always gives back a list.

The returned None is worth dwelling on, because it makes a whole category of mistake possible. `top = scores.sort()` leaves top as None while quietly sorting scores, and the failure surfaces somewhere else entirely. Every list method that mutates behaves this way: append, extend, insert, remove, sort, reverse, clear.

It also means you cannot chain them. numbers.append(4).append(5) is really None.append(5), which raises AttributeError.

In place against returning a copy
items.sort()Sorts items. Returns None. The original order is gone.
sorted(items)Returns a new sorted list. items is unchanged. Works on any iterable.
items.reverse()Reverses items in place. Returns None.
reversed(items)Returns an iterator, not a list. Wrap it in list() to see it.
items.sort(reverse=True)Descending. reverse=True is a sort direction, not a call to reverse().

key= sorts by something other than the value itself

By default Python sorts by comparing the elements. That works for numbers and strings, but a list of dictionaries has no natural order. key= takes a function, applies it to each element, and sorts by whatever comes back.

key=len sorts strings by length. key=str.casefold sorts names case-insensitively, which matters because uppercase letters sort before all lowercase ones by code point - a plain sort puts "Anita" and "Priya" before "chandu" and "ravi". key=lambda s: s["score"] sorts dictionaries by one field.

For two criteria, return a tuple. Python compares tuples left to right, so (-score, name) sorts by score descending - negating a number reverses its order - and then by name ascending for ties. That single expression is the whole of a ranking screen.

Python's sort is stable: elements with equal keys stay in the order they were already in. That is what makes sorting twice work - sort by name first, then by score, and equal scores come out alphabetical.

Common key functions
key=lenBy length. Works on strings, lists, anything with a length.
key=str.casefoldCase-insensitive text. Better than str.lower for non-English text.
key=lambda d: d["price"]By one field of a dictionary. The lambda is just "given an element, give me the sort value".
key=lambda d: (-d["score"], d["name"])Two criteria - score descending, then name ascending. Negation only works on numbers.
reverse=TrueFlips the whole ordering. Combine with key, or negate inside the key for finer control.

Tip: key= runs once per element, not once per comparison, so an expensive key function is cheaper than it looks.

b = a is a second name, and .copy() is only one level deep

This is the reference model from lesson 2 again. `copy = original` creates no list at all - it points a second name at the same object, so appending through either name shows up in both. It is not a bug in your code, it is what assignment means.

To get a real second list, ask for one: original.copy(), or original[:], or list(original). All three do the same thing; .copy() says so most clearly.

All three are shallow, which means the new list holds the same objects as the old one. For numbers and strings that is indistinguishable from a real copy, because you cannot change them anyway. For a list of lists it is not: copy[0].append("Django") changes the inner list, and both outer lists can see it, because there is only one inner list.

A copy that goes all the way down is a deep copy, from the copy module. That comes later in the course - for now, know that .copy() stops at the first level, and that this is fine right up until your list contains other lists.

Watch out: The three shallow copies are equivalent. If your list contains lists, none of them is enough.

Where lists get slow

A list stores its elements in order, so reaching one by index is instant no matter how long the list is, and adding to the end is effectively instant too. Those are the operations lists are built for.

Anything that touches the front is not. pop(0) and insert(0, x) have to shift every remaining element along by one, so the cost grows with the list. Draining a large queue with pop(0) is the classic version of this. collections.deque exists precisely for that, and you will meet it later.

Anything that searches is also linear: `x in items`, index(), count(), and remove() all walk the list until they find a match. On a handful of elements that is irrelevant. On a hundred thousand, checked inside a loop, it is the reason the page is slow.

The fix is usually a different data structure rather than cleverer list code. A set answers "is this present?" in constant time, and a dictionary does the same for "what is the value for this key?". Both are coming up in this module. Pick the structure from the operations you need, not out of habit.

What each operation costs
items[i]O(1) - instant, whatever the length.
items.append(x)O(1) amortised - occasionally it regrows, but on average instant.
items.pop()O(1) - from the end, nothing has to move.
x in itemsO(n) - walks until it finds a match. A set is O(1).
items.index(x) / .count(x) / .remove(x)O(n) - all of them scan.
items.insert(0, x) / items.pop(0)O(n) - every other element shifts.
items.sort()O(n log n) - Timsort, and close to O(n) on partly sorted data.

Syntax and examples

Combining and repeating
backend = ["Python", "FastAPI", "PostgreSQL"] frontend = ["React", "Next.js", "TypeScript"] all_courses = backend + frontend print(all_courses) # ['Python', 'FastAPI', 'PostgreSQL', 'React', 'Next.js', 'TypeScript'] print(backend) # unchanged - + built a new list # ['Python', 'FastAPI', 'PostgreSQL'] # extend does the same merge, but into the list you already have backend.extend(frontend) print(backend) # now six items print(frontend) # still three - only the left side changed # Repetition print([1, 2] * 3) # [1, 2, 1, 2, 1, 2] print(["student", "teacher"] * 2) # ['student', 'teacher', 'student', 'teacher'] print([] * 5) # [] - empty stays empty
Membership and comparison
courses = ["Python", "React", "Node.js"] print("Python" in courses) # True print("Java" in courses) # False requested = "React" if requested in courses: print("Course available") if "Java" not in courses: print("Java is not available") # Equality compares contents, in order print([1, 2, 3] == [1, 2, 3]) # True print([1, 2, 3] == [3, 2, 1]) # False - order counts print([1, 2] == [1, 2, 3]) # False - length counts # < and > compare element by element, left to right print([1, 2] < [1, 3]) # True - 1 == 1, then 2 < 3 print([1, 5] < [2, 1]) # True - decided on the first element print([1, 5, 100] < [1, 5, 200]) # True - decided on the third
append, extend, insert
items = [1, 2, 3] items.append(4) print(items) # [1, 2, 3, 4] items = [1, 2, 3] items.append([4, 5]) print(items) # [1, 2, 3, [4, 5]] <- one nested element items = [1, 2, 3] items.extend([4, 5]) print(items) # [1, 2, 3, 4, 5] <- two elements # extend takes any iterable, and a string is one letters = ["a", "b"] letters.extend("cd") print(letters) # ['a', 'b', 'c', 'd'] letters = ["a", "b"] letters.append("cd") print(letters) # ['a', 'b', 'cd'] numbers = [1, 2] numbers.extend((3, 4)) # a tuple works too print(numbers) # [1, 2, 3, 4] # insert places by index courses = ["Python", "Node.js"] courses.insert(1, "React") print(courses) # ['Python', 'React', 'Node.js'] courses.insert(0, "Git") # front print(courses) # ['Git', 'Python', 'React', 'Node.js'] nums = [1, 2, 3] nums.insert(100, 4) # past the end - appends, no error print(nums) # [1, 2, 3, 4]
remove, pop, clear, del
courses = ["Python", "React", "Python"] courses.remove("Python") # first match only print(courses) # ['React', 'Python'] # pop returns what it removed - that is the point of it courses = ["Python", "React", "Node.js"] last = courses.pop() print(last, courses) # Node.js ['Python', 'React'] courses = ["Python", "React", "Node.js"] middle = courses.pop(1) print(middle, courses) # React ['Python', 'Node.js'] tasks = ["Task A", "Task B", "Task C"] task = tasks.pop(0) print(f"Processing: {task}") # Processing: Task A # clear empties the list but keeps the object items = [1, 2, 3] items.clear() print(items) # [] # del is a statement, not a method items = [10, 20, 30] del items[1] print(items) # [10, 30] numbers = [10, 20, 30, 40, 50] del numbers[1:4] # a whole slice at once print(numbers) # [10, 50] items = [1, 2, 3] del items # deletes the name itself # print(items) -> NameError # Searching scores = [80, 90, 80, 70, 80] print(scores.count(80)) # 3 print(scores.index(90)) # 1 nums = [10, 20, 30, 20, 40] print(nums.index(20, 2)) # 3 - start searching at index 2
Sorting, and the None that catches everyone
numbers = [50, 10, 40, 20, 30] numbers.sort() print(numbers) # [10, 20, 30, 40, 50] numbers.sort(reverse=True) print(numbers) # [50, 40, 30, 20, 10] # sorted() leaves the original alone scores = [50, 10, 40, 20, 30] ranked = sorted(scores) print(ranked) # [10, 20, 30, 40, 50] print(scores) # [50, 10, 40, 20, 30] # The trap: sort() returns None result = [3, 1, 2].sort() print(result) # None # Reversing numbers = [1, 2, 3, 4] numbers.reverse() print(numbers) # [4, 3, 2, 1] print(list(reversed([1, 2, 3]))) # [3, 2, 1] - reversed() is an iterator # key= sorts by something computed courses = ["Python", "React", "JavaScript", "Go"] courses.sort(key=len) print(courses) # ['Go', 'React', 'Python', 'JavaScript'] names = ["ravi", "Anita", "chandu", "Priya"] names.sort(key=str.casefold) print(names) # ['Anita', 'chandu', 'Priya', 'ravi']
The worked example - ranking with a tuple key
students = [ {"name": "Ravi", "score": 90}, {"name": "Anita", "score": 90}, {"name": "Chandu", "score": 80}, {"name": "Priya", "score": 91}, ] # One criterion: highest score first students.sort(key=lambda student: student["score"], reverse=True) for student in students: print(student["name"], student["score"]) # Priya 91 # Ravi 90 # Anita 90 # Chandu 80 # Two criteria: score descending, then name alphabetically for ties. # A tuple key is compared left to right, and negating reverses a number. students.sort(key=lambda student: (-student["score"], student["name"])) for student in students: print(student["name"], student["score"]) # Priya 91 # Anita 90 # Ravi 90 # Chandu 80 # Stability: equal keys keep the order they already had products = [ {"name": "Laptop", "price": 75000}, {"name": "Mouse", "price": 1200}, {"name": "Monitor", "price": 15000}, ] print(sorted(products, key=lambda p: p["price"])[0]["name"]) # Mouse print(max(products, key=lambda p: p["price"])["name"]) # Laptop
Copying, aliasing, and the shallow limit
# Assignment is not a copy - two names, one list original = [1, 2, 3] alias = original alias.append(4) print(original) # [1, 2, 3, 4] <- both see it print(alias) # [1, 2, 3, 4] # A real copy - three equivalent ways original = [1, 2, 3] copied = original.copy() # clearest sliced = original[:] # same thing built = list(original) # same thing copied.append(4) print(original) # [1, 2, 3] print(copied) # [1, 2, 3, 4] # Shallow: the outer list is new, the inner lists are shared courses = [["Python", "FastAPI"], ["React", "Next.js"]] shallow = courses.copy() shallow[0].append("Django") print(courses[0]) # ['Python', 'FastAPI', 'Django'] <- changed too print(shallow[0]) # ['Python', 'FastAPI', 'Django'] # Built-ins that read a list without changing it prices = [1200, 4500, 999, 7500, 2300] print(max(prices), min(prices), sum(prices), len(prices)) # 7500 999 16499 5

Tip: max(), min(), sum(), and len() take a whole list and are almost always clearer than a loop that does the same thing. max() and min() accept key= too.

Watch out: Repetition with * on a list of lists repeats the reference, not the inner list - [[0]] * 3 gives three names for one list. Use a comprehension when the inner values are mutable.

Operators that work on lists

These build or test. None of them changes the list on the left, except +=.

+

Concatenate. Builds a new list; both operands must be lists.

a + b
*

Repeat the elements n times into a new list.

items * 3
+=

Extends in place. Not the same as items = items + other.

a += b
in

True if an equal element is present. Scans the list.

"Python" in courses
not in

The negation, and clearer than not (x in items).

"Java" not in courses
==

Same elements in the same order, same length.

[1, 2] == [1, 2]
<, >, <=, >=

Lexicographic - element by element, left to right.

[1, 2] < [1, 3]
del

A statement. Removes an index, a slice, or the name itself.

del items[1:4]

List methods

Everything in the top group changes the list and returns None. The bottom group returns a value.

append(x)

Add x as one element.

items.append(4)
extend(xs)

Add each element of any iterable.

items.extend([4, 5])
insert(i, x)

Add at index i, shifting the rest right.

items.insert(0, "Git")
remove(x)

Delete the first element equal to x. ValueError if absent.

items.remove("Python")
clear()

Remove every element, keeping the list object.

items.clear()
sort()

Sort in place. Takes key= and reverse=.

items.sort(key=len)
reverse()

Reverse in place.

items.reverse()
pop(i)

Remove and return - the last element, or the one at i.

last = items.pop()
index(x, start, stop)

Position of the first match. ValueError if absent.

items.index(20, 2)
count(x)

How many elements equal x.

scores.count(80)
copy()

A new list with the same elements. One level deep.

items.copy()

Try it yourself

The code does not change. Swap the content string and the program does something else entirely.

Feel the append/extend split

“Run items.append([4, 5]) and items.extend([4, 5]) on the same starting list, then print len() of each. Explain the difference in lengths out loud before reading on.”

Catch the None

“Write top = [3, 1, 2].sort() and print both top and the list. Then change it to sorted() and print both again.”

Break a case-sensitive sort

“Sort ["ravi", "Anita", "chandu", "Priya"] with no key, then with key=str.casefold. Work out why the first one groups the capitals together.”

Prove aliasing

“Set b = a, append to b, and print a. Then redo it with b = a.copy(). Predict each result before running it.”

Find the shallow edge

“Copy a list of lists, append to an inner list of the copy, and print the original. Then try the same with a list of plain numbers and explain why that one looks safe.”

What usually goes wrong

Assigning the result of sort()

sort() sorts the list and returns None. Assigning it throws away the list and stores None, and the crash happens later, wherever that name is used.

✗ ranked = scores.sort()
print(ranked[0])      # TypeError: None is not subscriptable
✓ ranked = sorted(scores)   # new list
# or
scores.sort()             # in place, no assignment
extend() with a string

A string is iterable, so extend adds one character per element. Nothing errors - you just find six entries where you expected one.

✗ tags = ["python"]
tags.extend("react")
# ['python', 'r', 'e', 'a', 'c', 't']
✓ tags.append("react")
# ['python', 'react']
Chaining mutating methods

append returns None, so the second call in a chain runs against None.

✗ numbers.append(4).append(5)
# AttributeError: NoneType has no attribute append
✓ numbers.append(4)
numbers.append(5)
Treating b = a as a copy

Assignment binds a second name to the same list. Changing either one changes what both names see.

✗ backup = courses
courses.append("Go")
print(backup)    # the "backup" has it too
✓ backup = courses.copy()
courses.append("Go")
print(backup)    # unchanged
Removing while looping over the same list

Deleting shifts every later element back by one, so the loop skips the element that moved into the gap. Build a new list instead.

✗ for number in numbers:
    if number == 1:
        numbers.remove(number)   # skips values
✓ result = []
for number in numbers:
    if number != 1:
        result.append(number)
remove() and index() on a missing value

Both raise ValueError rather than returning None or -1. Check membership first, or catch the error once you have met exceptions.

✗ courses.remove("Java")   # ValueError: list.remove(x): x not in list
✓ if "Java" in courses:
    courses.remove("Java")
Relying on set() to de-duplicate an ordered list

list(set(items)) removes duplicates but does not promise to keep the original order. If order matters, keep it explicitly.

✗ unique = list(set([1, 2, 2, 3, 1, 4]))   # order not guaranteed
✓ unique = []
for n in numbers:
    if n not in unique:
        unique.append(n)

Best practices

  • Reach for sorted() by default, and sort() only when you are sure nobody else holds the list.
  • Never assign the result of append, extend, insert, remove, sort, reverse, or clear.
  • Use append for one item and extend for many - and read extend calls carefully when the argument is a string.
  • Copy with .copy() rather than [:] - it says what it does, and the next reader does not have to think.
  • Check membership before remove() or index(), or be ready for ValueError.
  • Prefer a tuple key over sorting twice: key=lambda x: (-x["score"], x["name"]) is one pass and one line.
  • Build a new list when filtering; do not delete from the list you are iterating over.
  • When a list is large and you mostly ask "is this in it?", use a set instead.
  • Keep pop(0) and insert(0, x) out of loops over big lists - reach for collections.deque.

Practice

Write these yourself before opening anything. Getting them wrong first is most of how this sticks.

1.

Given frontend = ["React", "Next.js"] and backend = ["Python", "FastAPI"], produce ["React", "Next.js", "Python", "FastAPI"] without changing either original list.

Show hint

One operator builds a new list; the matching method would change the left-hand one.

Show solution
frontend = ["React", "Next.js"] backend = ["Python", "FastAPI"] combined = frontend + backend print(combined) # ['React', 'Next.js', 'Python', 'FastAPI'] print(frontend) # ['React', 'Next.js'] - untouched print(backend) # ['Python', 'FastAPI'] - untouched
2.

Starting from pattern = ["A", "B"], use repetition to produce ["A", "B", "A", "B", "A", "B"].

Show hint

The elements repeat, not the list - so the result has six elements, not three.

Show solution
pattern = ["A", "B"] repeated = pattern * 3 print(repeated) # ['A', 'B', 'A', 'B', 'A', 'B'] print(len(repeated)) # 6
3.

Sort the products below from cheapest to most expensive, then from most expensive to cheapest.

Show hint

The elements are dictionaries, so tell sort() which field to compare with key=.

Show solution
products = [ {"name": "Laptop", "price": 75000}, {"name": "Mouse", "price": 1200}, {"name": "Monitor", "price": 15000}, ] products.sort(key=lambda product: product["price"]) print([p["name"] for p in products]) # ['Mouse', 'Monitor', 'Laptop'] products.sort(key=lambda product: product["price"], reverse=True) print([p["name"] for p in products]) # ['Laptop', 'Monitor', 'Mouse']
4.

Sort ["Python", "React", "JavaScript", "Go", "TypeScript"] by name length, shortest first.

Show hint

key= takes a function. There is a built-in that already returns a length.

Show solution
courses = ["Python", "React", "JavaScript", "Go", "TypeScript"] courses.sort(key=len) print(courses) # ['Go', 'React', 'Python', 'JavaScript', 'TypeScript'] # JavaScript and TypeScript are both 10 - a stable sort keeps # them in the order they already had.
5.

Given scores = [75, 90, 60, 85, 95], produce a sorted version and then prove the original is unchanged.

Show hint

One of the two sorting tools never touches its input.

Show solution
scores = [75, 90, 60, 85, 95] ranked = sorted(scores) print(ranked) # [60, 75, 85, 90, 95] print(scores) # [75, 90, 60, 85, 95] - unchanged # scores.sort() would have given the same ranking but destroyed # the original order, and returned None rather than the list.
6.

Two API pages come back as separate lists of user dictionaries. Merge them into one list and sort it alphabetically by name.

Show hint

Merge with the operator that builds a new list, then sort by the "name" field.

Show solution
page_1 = [{"id": 1, "name": "Ravi"}, {"id": 2, "name": "Anita"}] page_2 = [{"id": 3, "name": "Chandu"}, {"id": 4, "name": "Priya"}] users = page_1 + page_2 users.sort(key=lambda user: user["name"]) for user in users: print(user["name"]) # Anita # Chandu # Priya # Ravi
Coding challenge

Product catalogue report

Take one catalogue of products and answer five questions about it, without destroying the original ordering that the rest of the report depends on.

It should
  • Print the products sorted by price, cheapest first.
  • Print the name of the most expensive product, and the name of the cheapest.
  • Build a list of only the products priced above ₹5,000.
  • Produce a sorted copy while leaving the original list in its original order - and prove it.
  • Rank the products by price descending, falling back to name alphabetically when two prices are equal.
products = [ {"name": "Laptop", "price": 75000}, {"name": "Mouse", "price": 1200}, {"name": "Keyboard", "price": 2500}, {"name": "Monitor", "price": 15000}, {"name": "Webcam", "price": 2500}, ] # 1. Sorted by price, cheapest first # 2. Most expensive and cheapest # 3. Only the products above 5000 # 4. A sorted copy, original untouched # 5. Price descending, then name for ties
Show one solution
products = [ {"name": "Laptop", "price": 75000}, {"name": "Mouse", "price": 1200}, {"name": "Keyboard", "price": 2500}, {"name": "Monitor", "price": 15000}, {"name": "Webcam", "price": 2500}, ] # 1. Sorted by price - sorted() so the original order survives for step 4 by_price = sorted(products, key=lambda product: product["price"]) print([p["name"] for p in by_price]) # ['Mouse', 'Keyboard', 'Webcam', 'Monitor', 'Laptop'] # 2. max() and min() take the same key= that sort() does print(max(products, key=lambda p: p["price"])["name"]) # Laptop print(min(products, key=lambda p: p["price"])["name"]) # Mouse # 3. Filtering - build a new list rather than deleting from this one premium = [] for product in products: if product["price"] > 5000: premium.append(product) print([p["name"] for p in premium]) # ['Laptop', 'Monitor'] # 4. The original is still in its original order print([p["name"] for p in products]) # ['Laptop', 'Mouse', 'Keyboard', 'Monitor', 'Webcam'] # 5. A tuple key: price descending, then name ascending. # Keyboard and Webcam both cost 2500, so the name decides. ranked = sorted(products, key=lambda p: (-p["price"], p["name"])) for product in ranked: print(product["name"], product["price"]) # Laptop 75000 # Monitor 15000 # Keyboard 2500 # Webcam 2500 # Mouse 1200

Key points

  • append adds one object; extend adds each element of an iterable - and a string is an iterable.
  • + and * build new lists; extend, +=, insert, remove, sort, and reverse change the list you have.
  • Every mutating list method returns None, so never assign its result and never chain it.
  • sort() rearranges in place; sorted() returns a new list and accepts any iterable.
  • key= decides what to sort by; a tuple key gives you a second criterion, and negating a number reverses it.
  • Python's sort is stable, so equal keys keep the order they already had.
  • b = a is a second name for one list. .copy(), [:], and list() each give a real second list.
  • All three copies are shallow - nested lists are still shared.
  • Index access and append are instant; searching, and anything at the front of the list, costs more as the list grows.
  • remove() and index() raise ValueError when the value is absent.

Quick check before you move on

a = [1, 2]; b = [3, 4]; c = a + b - what do print(a) and print(c) show?
[1, 2] and [1, 2, 3, 4]. + builds a new list and leaves both operands alone.
items = [1, 2]; items.append([3, 4]) - what is items, and what is len(items)?
[1, 2, [3, 4]], and len is 3. append adds the list as a single element.
numbers = [3, 1, 2]; result = numbers.sort() - what are result and numbers?
result is None and numbers is [1, 2, 3]. sort() changes the list and returns nothing.
numbers = [3, 1, 2]; result = sorted(numbers) - what are result and numbers?
result is [1, 2, 3] and numbers is still [3, 1, 2]. sorted() never touches its input.
numbers = [1, 2, 3]; other = numbers; other.append(4) - what does print(numbers) show?
[1, 2, 3, 4]. Assignment made a second name, not a second list.
letters = ["a", "b"]; letters.extend("cd") - what is letters?
['a', 'b', 'c', 'd']. extend walks the string and adds each character separately.
What does [1, 5] < [2, 1] evaluate to, and why?
True. Comparison goes left to right and 1 < 2 settles it, so the second elements are never compared.

Interview questions

What is the difference between append() and extend()?

append(x) adds x as a single element, whatever it is. extend(xs) iterates xs and adds each element. append([4, 5]) grows the list by one; extend([4, 5]) grows it by two.

What is the difference between sort() and sorted()?

sort() is a list method that reorders the list in place and returns None. sorted() is a built-in that accepts any iterable, leaves it unchanged, and returns a new list.

Why does list.sort() return None?

Because it mutates rather than producing a value. Python returns None from mutating methods deliberately, so you cannot mistake an in-place operation for one that gives you a new object.

What does list.copy() create, and what are its limits?

A shallow copy - a new outer list holding the same element objects. For immutable elements that is indistinguishable from a full copy; for nested lists the inner lists are still shared, so a deep copy is needed.

What happens when you write b = a for two lists?

Nothing is copied. Both names refer to the same list object, so a mutation through either is visible through both. a is b is True.

What is the difference between remove() and pop()?

remove(value) deletes the first element equal to the value and returns nothing, raising ValueError if it is absent. pop(index) deletes by position and returns the removed element, defaulting to the last one.

Are list comparisons order-sensitive?

Yes. == requires the same elements in the same positions, so [1, 2, 3] == [3, 2, 1] is False. Ordering comparisons are lexicographic - element by element, left to right.

What does the key parameter do in sorting?

It is a function applied to each element to produce the value that is actually compared. It runs once per element, not once per comparison. Returning a tuple gives multi-level sorting.

What is a stable sort, and why does it matter?

A stable sort preserves the relative order of elements with equal keys. Python's sort is stable, which is what lets you sort by a secondary field first and a primary field second and get a correct two-level ordering.

Why can pop(0) be inefficient on a large list?

A list stores elements contiguously, so removing the first one shifts every remaining element back by one - O(n) per call. Draining a large queue that way is O(n squared). collections.deque does it in constant time.

When would you choose a set over a list?

When you need fast membership tests or automatic de-duplication. `x in list` is O(n); `x in set` is O(1). The trade-off is that a set is unordered and holds only hashable values.

How would you remove duplicates while preserving order?

Not with list(set(items)), which gives no ordering guarantee. Iterate and append only values not already seen, or use dict.fromkeys(items), which keeps insertion order on Python 3.7 and later.

Quiz

  1. 1.

    Which single call merges other into items without creating a new list?

  2. 2.

    You need the removed value back. Which removal method do you use?

  3. 3.

    How do you sort names so that case does not affect the order?

  4. 4.

    What is the difference between items.clear() and del items?

  5. 5.

    Why can the same list appear to change through two different variables?

Comments

Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.

Loading comments...