Lists
Ordered, mutable, index-based - creating, slicing, changing, and nesting.
What you will be able to do
- Create lists, and say what "ordered" and "allows duplicates" buy you
- Access elements with positive and negative indexes, and avoid IndexError
- Extract parts of a list with slicing, including the step and the reverse idiom
- Change a list in place - assign to an index, add, remove, and clear
- Iterate a list directly, and with enumerate() when the position matters
- Read nested lists and lists of dictionaries, the shape most API data arrives in
- Explain why b = a shares one list, and copy safely when you need two
The idea, in plain English
A list is an ordered collection held in one variable. Instead of course1, course2, and course3, you write ["Python", "JavaScript", "React"] and keep them together - which means you can count them, loop over them, and pass them around as one thing.
Three properties define it. It is ordered, so position is meaningful and stable. It allows duplicates, unlike the set you will meet in lesson 18. And it is mutable - you can change it after it exists, which is the difference from the string in lesson 5 and from the tuple in lesson 17.
Mutability is where lists earn their place and where they surprise people. A string method hands you a new string and leaves the original alone; a list method usually changes the list you called it on and hands you nothing back. Both behaviours are useful. Confusing the two is the most common list bug there is.
This lesson covers the shape of a list and the operations you reach for daily. Lesson 16 goes further into the method set - sorting with a key, the performance of each operation, and the copying rules in full - so where something is only sketched here, that is where the rest of it lives.
Worked example: Totalling a shopping cart that arrives as a list of dictionaries.
Indexing, from both ends
Positions start at 0, so the first element is items[0] and the third is items[2]. Read the index as "how far from the start", which is why the first element is zero steps in.
Negative indexes count back from the end, and items[-1] is the last element regardless of how long the list is. That saves you writing items[len(items) - 1], and it keeps working when the list grows.
An index that does not exist raises IndexError, and it raises immediately rather than returning None - so a wrong index is a loud failure, which is the helpful kind. The valid range is 0 to len(items) - 1, and -1 to -len(items) going backwards.
len() gives the number of elements, counting from one rather than zero. A three-element list has len 3 and a highest index of 2, and that off-by-one is worth saying out loud once.
courses[0]"Python" - the first element. Indexes start at zero.courses[1]"React" - the second, not the first.courses[-1]"Node.js" - the last, whatever the length.courses[-2]"React" - second from the end.courses[3]IndexError. Valid indexes stop at len - 1, which is 2.len(courses)3 - a count, so one more than the highest index.Tip: items[-1] is the idiomatic way to reach the last element. items[len(items) - 1] works but says less.
Slicing, and the stop that is excluded
A slice takes a range rather than one element: items[start:stop] gives you a new list from start up to but not including stop. numbers[1:4] returns three elements - indexes 1, 2, and 3.
That exclusive stop looks arbitrary and is not. It makes the length of the slice exactly stop minus start, and it makes items[:n] and items[n:] fit together perfectly with no overlap and nothing missing.
Leave either end out and Python fills it in: items[:3] means from the beginning, items[2:] means to the end, and items[:] means the whole thing - which is one of the three ways to copy a list.
A third number is the step. items[::2] takes every second element, and items[::-1] walks backwards, which is the standard way to get a reversed copy. Unlike reverse(), it leaves the original alone, and unlike an index, a slice never raises - an out-of-range slice just gives you what exists.
numbers[1:4][20, 30, 40] - starts at 1, stops before 4.numbers[:3][10, 20, 30] - from the beginning.numbers[2:][30, 40, 50] - to the end.numbers[:]The whole list, as a new list. One way to copy.numbers[::2][10, 30, 50] - every second element.numbers[::-1][50, 40, 30, 20, 10] - a reversed copy, original untouched.numbers[2:99][30, 40, 50] - no error. Slices clamp; indexes do not.Mutable - changing a list in place
You can assign straight to a position: courses[1] = "Angular" replaces the second element. The list stays the same object; only its contents change. A string will not let you do this at all, which is the practical meaning of mutable against immutable.
To grow it, append() adds one element to the end and extend() adds each element of an iterable - so append([4, 5]) makes the list one longer with a list inside it, while extend([4, 5]) makes it two longer. insert(i, x) puts something at a position and shifts the rest along.
To shrink it there are three tools with three different jobs. remove(value) deletes the first element equal to that value, and raises ValueError if there is none. pop(index) deletes by position and gives the element back, defaulting to the last. del items[i] deletes by position and gives nothing back. clear() empties it entirely.
One thing to take on trust for now and meet properly in lesson 16: these methods return None rather than the changed list. `result = numbers.sort()` leaves result as None while quietly sorting numbers. Never assign the result of a method that modifies the list.
items[i] = xReplace one element. IndexError if i does not exist.items.append(x)Add x to the end as a single element.items.extend(xs)Add each element of an iterable.items.insert(i, x)Add at position i, shifting the rest right.items.remove(x)Delete the first element equal to x. ValueError if absent.items.pop(i)Delete by index and return it. No argument means the last.del items[i]Delete by index, returning nothing. Works on a slice too.items.clear()Remove everything. The list object stays.Watch out: Do not remove from a list you are looping over - deleting shifts the later elements back and the loop skips one. Build a new list instead.
Nested lists, and lists of dictionaries
A list can hold other lists, which gives you a grid. matrix[1] is the second row - itself a list - and matrix[1][2] is the third element of that row. Read the brackets left to right: pick the row, then pick within it.
Looping over a nested list needs a loop inside a loop: the outer one walks the rows, the inner one walks each row. That is the same nesting you saw with loops in lesson 12, now with a reason to exist.
The shape you will actually meet most is a list of dictionaries - one dictionary per record, all with the same keys. Every JSON API response and every database result set looks like this, and it is why lists and dictionaries together matter more than either alone.
Iterating it reads well: `for product in products:` gives you one record at a time, and product["price"] reaches into it. Dictionaries get their own lesson next, so treat the square-bracket lookup as borrowed syntax for now.
A name is a reference, not a list
This is the reference model from lesson 2, and lists are where it first has visible consequences. `other = numbers` does not build a second list. It points a second name at the one that already exists.
So appending through either name shows up through both, because there is only one list. Nothing is broken - that is simply what assignment means in Python, and it is why a "backup" made this way protects nothing.
For an independent list, ask for one: numbers.copy(), or numbers[:], or list(numbers). All three do the same thing, and .copy() states the intent most plainly.
All three are shallow - the new list holds the same objects as the old one. With numbers and strings you cannot tell, because those cannot change anyway. With a list of dictionaries you can: editing copy[0]["name"] changes the original too, because both lists point at the same dictionary. Lesson 16 takes this further.
b = aOne list, two names. Every change is visible through both.b = a.copy()A genuine second list. The clearest of the three.b = a[:]The same thing, written as a full slice.b = list(a)The same again, via the constructor.a is bTrue only when they are one object. After a copy it is False.Nested contentsShared by all three copies - the copy is only one level deep.Syntax and examples
courses = ["Python", "JavaScript", "React"]
numbers = [10, 20, 30, 40]
users = [] # empty, to fill later
print(len(courses)) # 3
print(len(users)) # 0
# Order is preserved exactly as written
print(courses) # ['Python', 'JavaScript', 'React']
# Duplicates are allowed - a set would collapse these, a list does not
tags = ["python", "backend", "python", "api"]
print(len(tags)) # 4
# Mixed types are legal
data = [100, "Python", True, 10.5, None]
# Including other containers
items = ["Python", 100, ["React", "Node.js"], {"level": "beginner"}]
# Legal is not the same as wise - code that reads a list is much
# simpler when every element has the same shape.courses = ["Python", "React", "Node.js"]
# index: 0 1 2
# value: Python React Node.js
# negative: -3 -2 -1
print(courses[0]) # Python
print(courses[1]) # React <- the second, not the first
print(courses[-1]) # Node.js <- the last, whatever the length
print(courses[-2]) # React
first = courses[0]
last = courses[-1]
print(first, last) # Python Node.js
# Out of range raises straight away
# print(courses[5]) -> IndexError: list index out of range
# len() counts from one; the highest index is one less
print(len(courses)) # 3
print(courses[len(courses) - 1]) # Node.js - what [-1] says more clearlynumbers = [10, 20, 30, 40, 50]
print(numbers[1:4]) # [20, 30, 40] - 1, 2, 3; stop is excluded
print(numbers[:3]) # [10, 20, 30] - from the beginning
print(numbers[2:]) # [30, 40, 50] - to the end
print(numbers[:]) # the whole list, as a new list
# The two halves fit together with nothing missing or repeated
print(numbers[:2] + numbers[2:] == numbers) # True
# A step takes every nth element
print(numbers[::2]) # [10, 30, 50]
# A negative step walks backwards - the reverse idiom
print(numbers[::-1]) # [50, 40, 30, 20, 10]
print(numbers) # unchanged - the slice built a new list
# Slices clamp instead of raising, unlike a plain index
print(numbers[2:99]) # [30, 40, 50]
print(numbers[10:20]) # [] - no error
# A slice is one of the three ways to copy
copy = numbers[:]
copy.append(60)
print(numbers) # [10, 20, 30, 40, 50] - untouched
print(copy) # [10, 20, 30, 40, 50, 60]courses = ["Python", "React", "Node.js"]
courses[1] = "Angular" # assign straight to a position
print(courses) # ['Python', 'Angular', 'Node.js']
# A string will not allow the equivalent - that is immutability
# name = "Python"; name[0] = "J" -> TypeError
# Growing
courses.append("AWS") # one element on the end
courses.extend(["Go", "Rust"]) # each element of an iterable
courses.insert(0, "Git") # at a position, shifting the rest
print(len(courses)) # 7
# append adds one thing, whatever it is
numbers = [1, 2, 3]
numbers.append([4, 5])
print(numbers) # [1, 2, 3, [4, 5]] <- four elements
numbers = [1, 2, 3]
numbers.extend([4, 5])
print(numbers) # [1, 2, 3, 4, 5] <- five
# Shrinking - three tools, three jobs
courses = ["Python", "React", "Node.js"]
courses.remove("React") # by value; ValueError if absent
print(courses) # ['Python', 'Node.js']
courses = ["Python", "React", "Node.js"]
removed = courses.pop(1) # by index, and hands it back
print(removed, courses) # React ['Python', 'Node.js']
last = courses.pop() # no index means the last one
print(last) # Node.js
courses = ["Python", "React", "Node.js"]
del courses[1] # by index, returns nothing
print(courses) # ['Python', 'Node.js']
courses.clear()
print(courses) # [] - empty, but still a list
# Searching before removing avoids the error
courses = ["Python", "React"]
if "Java" in courses:
courses.remove("Java")
print(courses.index("React")) # 1
print([10, 20, 10, 30, 10].count(10)) # 3courses = ["Core Python", "FastAPI", "React"]
# Direct iteration - you rarely need the index at all
for course in courses:
print(course)
# enumerate() when the position matters, starting wherever you like
for index, course in enumerate(courses, start=1):
print(f"{index}. {course}")
# 1. Core Python
# 2. FastAPI
# 3. React
# This works but says less, and is easy to get wrong:
# for index in range(len(courses)):
# print(courses[index])
# Do not remove from the list you are iterating over.
# Deleting shifts the later elements back, so the loop skips one:
numbers = [1, 2, 3, 4, 5]
for number in numbers:
if number % 2 == 0:
numbers.remove(number)
print(numbers) # [1, 3, 5] here, but the 4 was nearly missed
# Build a new list instead - always correct, and easier to read
numbers = [1, 2, 3, 4, 5]
odd = []
for number in numbers:
if number % 2 != 0:
odd.append(number)
print(odd) # [1, 3, 5]scores = [85, 90, 72, 95, 88]
print(len(scores)) # 5
print(sum(scores)) # 430
print(min(scores)) # 72
print(max(scores)) # 95
print(sum(scores) / len(scores)) # 86.0
print(sorted(scores)) # [72, 85, 88, 90, 95] - a new list
print(any([False, True])) # True - at least one truthy
print(all([True, True])) # True - every one truthy
print(any([])) # False
print(all([])) # True - vacuously, as in lesson 9
# sum() of an empty list is 0, but the average divides by zero
empty = []
print(sum(empty)) # 0
# print(sum(empty) / len(empty)) -> ZeroDivisionError
# So guard it. An empty list is falsy, which makes the check read well.
if not empty:
print("No scores yet")
else:
print(sum(empty) / len(empty))
# Prefer "if not items" over "if len(items) == 0"# A list of lists is a grid
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
print(matrix[0]) # [1, 2, 3] - the first row
print(matrix[1][2]) # 6 - row 1, then element 2
course_modules = [
["Introduction", "Variables", "Data Types"],
["Lists", "Tuples", "Sets"],
]
print(course_modules[0][1]) # Variables
for module in course_modules:
for lesson in module:
print(lesson)
# The shape real data arrives in: a list of dictionaries,
# one per record, all with the same keys
cart = [
{"name": "Laptop", "price": 75000},
{"name": "Mouse", "price": 1200},
{"name": "Keyboard", "price": 2500},
]
total = 0
for product in cart:
total = total + product["price"]
print(total) # 78700
cart.append({"name": "Monitor", "price": 15000})
total = 0
for product in cart:
total = total + product["price"]
print(total) # 93700
for index, product in enumerate(cart, start=1):
print(f"{index}. {product['name']} - {product['price']}")numbers = [1, 2, 3]
other = numbers # a second name, not a second list
other.append(4)
print(numbers) # [1, 2, 3, 4] <- both see it
print(other) # [1, 2, 3, 4]
print(numbers is other) # True - one object
# A real copy
numbers = [1, 2, 3]
other = numbers.copy() # or numbers[:] or list(numbers)
other.append(4)
print(numbers) # [1, 2, 3]
print(other) # [1, 2, 3, 4]
print(numbers is other) # False - two objects
print(numbers == other) # False - and different contents now
# All three copies are shallow: the new list holds the same objects.
# With numbers you cannot tell, because a number cannot change.
# With dictionaries inside, you can:
users = [{"name": "Ravi"}, {"name": "Anita"}]
copied = users.copy()
copied[0]["name"] = "Chandu"
print(users[0]) # {'name': 'Chandu'} <- changed too
# The lists are separate, but they point at the same dictionaries.
copied.append({"name": "Priya"})
print(len(users), len(copied)) # 2 3Tip: An empty list is falsy, so `if not items:` is the idiomatic empty check. It reads better than len(items) == 0 and works for strings and dictionaries too.
Watch out: A slice never raises for an out-of-range bound - items[10:20] on a short list is just []. A plain index does raise. That asymmetry hides bugs, so do not use a slice to test whether something exists.
List operations
The everyday set. Lesson 16 covers sorting with a key, the copying rules, and what each one costs.
CreateSquare brackets, empty or filled.
items = []
AccessBy position, from 0. IndexError if out of range.
items[0]
LastNegative indexes count back from the end.
items[-1]
SliceA new list from start up to but not including stop.
items[1:4]
Reverse copyA negative step. The original is untouched.
items[::-1]
UpdateAssign straight to a position.
items[0] = value
Add oneTo the end, as a single element.
items.append(x)
Add manyEach element of an iterable.
items.extend(xs)
InsertAt a position, shifting the rest right.
items.insert(i, x)
Remove by valueThe first match. ValueError if absent.
items.remove(x)
Remove by indexAnd hand it back. No argument means the last.
items.pop(i)
DeleteBy index or slice, returning nothing.
del items[i]
ClearEmpty it, keeping the list object.
items.clear()
SearchMembership test. Scans the list.
x in items
PositionIndex of the first match. ValueError if absent.
items.index(x)
CountHow many elements equal x.
items.count(x)
Sort / reverseBoth in place, and both return None.
items.sort()
CopyA new list, one level deep.
items.copy()
Built-in functions that take a list
These read the list and return something new - none of them changes it.
len(items)How many elements. Counts from one, so the last index is len - 1.
len(scores)
sum(items)The total. 0 for an empty list, which is why averages need a guard.
sum(scores)
min(items)The smallest. ValueError on an empty list.
min(scores)
max(items)The largest. Also accepts key=, as in lesson 16.
max(scores)
sorted(items)A new sorted list. The original keeps its order.
sorted(scores)
any(items)True if at least one element is truthy. False for an empty list.
any(flags)
all(items)True if every element is truthy - and True for an empty list.
all(flags)
enumerate(items)Yields (index, value) pairs. Takes start= to begin at 1.
enumerate(items, start=1)
list(x)Builds a list from any iterable - a string, a range, a tuple.
list(range(5))
Try it yourself
The code does not change. Swap the content string and the program does something else entirely.
“Make a three-element list and print items[2], then items[3]. Read the IndexError, then work out the highest valid index from len().”
“Print numbers[1:4] and count the elements you got back. Then check that numbers[:2] + numbers[2:] equals the original.”
“Run append([4, 5]) and extend([4, 5]) on two copies of [1, 2, 3] and print len() of each. Explain the difference before reading on.”
“Loop over [1, 2, 3, 4, 5] and remove every even number inside the loop. Print the result and work out which element the loop skipped and why.”
“Copy a list of dictionaries with .copy(), change a key in the copy, and print the original. Then do the same with a list of plain numbers and explain why that one looks safe.”
What usually goes wrong
Indexes start at 0, so items[1] is the second element. Counting from one gives you an off-by-one that raises nothing and silently reads the wrong value.
✗ courses = ["Python", "React", "Node.js"]
print(courses[1]) # React, not Python✓ print(courses[0]) # Python - the first
print(courses[-1]) # Node.js - the lastappend puts its argument in as one element whatever it is; extend walks the argument and adds each element. The difference only shows up in the length.
✗ items = [1, 2, 3]
items.append([4, 5])
print(len(items)) # 4 - a list got nested✓ items.extend([4, 5])
print(len(items)) # 5 - two elements addedappend, extend, insert, remove, sort, reverse, and clear all change the list and return None. Assigning that throws the list away and stores None.
✗ numbers = [3, 1, 2]
result = numbers.sort()
print(result) # None✓ numbers.sort() # in place, no assignment
# or
result = sorted(numbers) # a new sorted listDeleting shifts every later element back by one while the loop counter moves forward, so an element gets skipped. It usually half-works, which is worse than failing.
✗ for number in numbers:
if number % 2 == 0:
numbers.remove(number)✓ odd = []
for number in numbers:
if number % 2 != 0:
odd.append(number)Assignment binds a second name to the same list, so the "backup" changes whenever the original does.
✗ backup = courses
courses.append("Go")
print(backup) # it has Go too✓ backup = courses.copy()
courses.append("Go")
print(backup) # unchangedsum([]) is 0 quite happily, but len([]) is 0 too, and dividing by it raises ZeroDivisionError. Empty input is normal, not exceptional.
✗ average = sum(scores) / len(scores)
# ZeroDivisionError when scores is empty✓ if not scores:
average = 0
else:
average = sum(scores) / len(scores)Both raise ValueError rather than returning None or -1. Check membership first, or catch it once you have met exceptions.
✗ courses.remove("Java")
# ValueError: list.remove(x): x not in list✓ if "Java" in courses:
courses.remove("Java")Best practices
- Name lists for what they hold, in the plural - courses, scores, users - not data or x.
- Iterate directly with `for course in courses`, and use enumerate() only when you genuinely need the position.
- Use items[-1] for the last element rather than items[len(items) - 1].
- Check emptiness with `if not items:` rather than comparing len() to zero.
- Never remove from a list you are iterating over - build a new list instead.
- Never assign the result of append, extend, insert, remove, sort, reverse, or clear.
- Copy with .copy() when you need an independent list, and remember it is only one level deep.
- Keep the elements of a list the same shape; mixed-type lists are legal but hard to process.
- Guard any average or division against the empty-list case.
Practice
Write these yourself before opening anything. Getting them wrong first is most of how this sticks.
Create a list of ["Python", "JavaScript", "TypeScript", "React", "Node.js"]. Print the first item, the last item, and the length; replace "React" with "Next.js"; then print the final list.
Show hintHide hint
Negative indexing gives the last item. Assign straight to a position to replace one.
Show solutionHide solution
languages = ["Python", "JavaScript", "TypeScript", "React", "Node.js"]
print(languages[0]) # Python
print(languages[-1]) # Node.js
print(len(languages)) # 5
languages[3] = "Next.js" # React was at index 3
print(languages)
# ['Python', 'JavaScript', 'TypeScript', 'Next.js', 'Node.js']Starting from cart = ["Laptop", "Mouse", "Keyboard"], add "Monitor", remove "Mouse", check whether "Keyboard" is present, print how many products there are, and print the final cart.
Show hintHide hint
append to add one, remove to delete by value, and `in` for the check.
Show solutionHide solution
cart = ["Laptop", "Mouse", "Keyboard"]
cart.append("Monitor")
cart.remove("Mouse")
if "Keyboard" in cart:
print("Keyboard is in the cart")
print(len(cart)) # 3
print(cart) # ['Laptop', 'Keyboard', 'Monitor']For scores = [78, 92, 85, 67, 95, 88], find the highest, the lowest, the total, how many there are, and the average.
Show hintHide hint
Four built-ins do all of this without a single loop.
Show solutionHide solution
scores = [78, 92, 85, 67, 95, 88]
print(max(scores)) # 95
print(min(scores)) # 67
print(sum(scores)) # 505
print(len(scores)) # 6
print(sum(scores) / len(scores)) # 84.16666666666667
# Guard the empty case if the list could be empty
average = sum(scores) / len(scores) if scores else 0For numbers = [10, 20, 30, 40, 50, 60, 70], print the first three, the last three, the elements from index 2 to 5, every second element, and the list reversed.
Show hintHide hint
All five are slices. Remember the stop index is excluded.
Show solutionHide solution
numbers = [10, 20, 30, 40, 50, 60, 70]
print(numbers[:3]) # [10, 20, 30]
print(numbers[-3:]) # [50, 60, 70]
print(numbers[2:5]) # [30, 40, 50] - stops before index 5
print(numbers[::2]) # [10, 30, 50, 70]
print(numbers[::-1]) # [70, 60, 50, 40, 30, 20, 10]
print(numbers) # unchanged - every slice built a new listFrom scores = [85, 92, 67, 45, 91, 73, 88], count how many are 80 or above and build a new list containing only those.
Show hintHide hint
Build the filtered list first, then its length answers the count.
Show solutionHide solution
scores = [85, 92, 67, 45, 91, 73, 88]
high = []
for score in scores:
if score >= 80:
high.append(score)
print(high) # [85, 92, 91, 88]
print(len(high)) # 4
# Building a new list, rather than removing from the original,
# is what keeps this correct.Total the cart below, then add a Monitor at 15000 and total it again.
Show hintHide hint
Loop over the records and reach into each with its key.
Show solutionHide solution
cart = [
{"name": "Laptop", "price": 75000},
{"name": "Mouse", "price": 1200},
{"name": "Keyboard", "price": 2500},
]
total = 0
for product in cart:
total = total + product["price"]
print(total) # 78700
cart.append({"name": "Monitor", "price": 15000})
total = 0
for product in cart:
total = total + product["price"]
print(total) # 93700Course manager
Run a short sequence of edits over one list of courses, then display the result. Every step uses an operation from this lesson, and the order of the steps matters.
- Start from ["Python", "JavaScript", "React"] and add "Node.js" and then "AWS".
- Replace "JavaScript" with "TypeScript" by finding its position rather than hard-coding an index.
- Remove "React", guarding against it not being there.
- Sort the courses alphabetically.
- Display them numbered from 1, and print the final count.
courses = ["Python", "JavaScript", "React"]
# 1. Add Node.js, then AWS
# 2. Replace JavaScript with TypeScript, found by position
# 3. Remove React, safely
# 4. Sort
# 5. Display numbered from 1, then the count
Show one solutionHide solution
courses = ["Python", "JavaScript", "React"]
# 1. append adds one element at a time
courses.append("Node.js")
courses.append("AWS")
print(courses)
# ['Python', 'JavaScript', 'React', 'Node.js', 'AWS']
# 2. index() finds the position, so the code survives a reordering.
# Check membership first - index() raises ValueError otherwise.
if "JavaScript" in courses:
position = courses.index("JavaScript")
courses[position] = "TypeScript"
# 3. The same guard before removing by value
if "React" in courses:
courses.remove("React")
print(courses)
# ['Python', 'TypeScript', 'Node.js', 'AWS']
# 4. sort() rearranges in place and returns None,
# so it is a statement on its own - never an assignment.
courses.sort()
# 5. enumerate(start=1) gives human numbering without a counter
for index, course in enumerate(courses, start=1):
print(f"{index}. {course}")
# 1. AWS
# 2. Node.js
# 3. Python
# 4. TypeScript
print(f"{len(courses)} courses") # 4 coursesKey points
- A list is ordered, allows duplicates, and is mutable - those three facts explain everything else.
- Indexes start at 0, so the last valid index is len(items) - 1, and an out-of-range index raises IndexError.
- Negative indexes count back, and items[-1] is the last element whatever the length.
- A slice runs from start up to but not including stop, and always returns a new list.
- items[:] copies, items[::-1] reverses, and a slice clamps instead of raising.
- append adds one element; extend adds each element of an iterable.
- remove deletes by value, pop deletes by index and returns it, del deletes by index and returns nothing.
- Methods that change the list return None - never assign their result.
- Never remove from a list you are iterating over; build a new list instead.
- A list can hold lists and dictionaries; a list of dictionaries is how API and database data arrives.
- b = a gives one list two names. .copy(), [:], and list() each give a genuine second list.
- All three copies are shallow, so nested dictionaries and lists are still shared.
- An empty list is falsy, so `if not items:` is the idiomatic empty check - and sum([]) is 0 while len([]) breaks an average.
Quick check before you move on
Interview questions
What is a list in Python?
An ordered, mutable sequence that can hold values of any type, including other lists. Position is meaningful, duplicates are allowed, and elements can be replaced, added, or removed after creation.
Are Python lists mutable, and why does that matter?
Yes. You can assign to an index, and methods such as append and sort change the list in place rather than returning a new one. It matters because any other name bound to that list sees the change too.
How do you access the last element, and why is that the preferred form?
items[-1]. Negative indexes count back from the end, so it is correct whatever the length, where a literal index breaks as soon as the list changes.
What does items[1:4] return, and why is the stop excluded?
A new list with the elements at indexes 1, 2, and 3. Excluding the stop makes the slice length exactly stop minus start, and makes items[:n] and items[n:] partition the list with no overlap or gap.
What is the difference between append() and extend()?
append(x) adds x as a single element whatever it is, so appending a list nests it. extend(xs) iterates xs and adds each element, so the list grows by the length of the argument.
What is the difference between remove() and pop()?
remove(value) searches for the first element equal to the value, deletes it, and returns nothing, raising ValueError if it is absent. pop(index) deletes by position and returns the removed element, defaulting to the last.
What is the difference between sort() and sorted()?
sort() is a list method that reorders in place and returns None. sorted() is a built-in that takes any iterable, leaves it unchanged, and returns a new list.
What happens when you assign one list to another?
Nothing is copied. Both names refer to the same list object, so a mutation through either is visible through both, and `a is b` is True.
How do you copy a list, and what are the limits?
a.copy(), a[:], or list(a) - all equivalent, and all shallow. The new list holds the same element objects, so nested lists and dictionaries are still shared and a deep copy is needed to separate them.
Why is modifying a list while iterating over it a problem?
The iterator advances by position while deletions shift the remaining elements backwards, so elements get skipped. It often produces a partially correct result rather than an error, which makes it harder to spot.
How do you check whether a list is empty, and why that way?
`if not items:`. An empty list is falsy, so this is idiomatic, reads as a sentence, and works the same for strings, dictionaries, and sets.
Can a list contain different data types, and should it?
It can - [10, "Python", True, None] is valid, because a list holds references to objects of any type. In practice, code that processes a list is far simpler when every element has the same shape.
Quiz
- 1.
How do you get the last element of a list of unknown length?
- 2.
Which three ways copy a list, and what do they have in common?
- 3.
What is the difference between remove(), pop(), and del?
- 4.
Why should you not delete from a list while looping over it?
- 5.
Why does a slice with an out-of-range bound not raise?
Comments
Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.
Loading comments...
AI
System Design
Backend
- GraphQL8 modules · 69 lessons planned
- Core Python13 modules · 75 lessons planned
- FastAPI5 sections · 20 lessons
- Node.js14 modules · 206 lessons planned
- Node.js Performance7 chapters · 36 topics
- Event Loop Lifecycle6 phases · 3 scenarios
- Docker & Containerization11 modules · 144 lessons planned
- AWS for Developers14 modules · 219 lessons planned
- CI/CD & DevOps Automation10 modules · 134 lessons planned