All 57 lessons

Lesson 12 of 57 · Data Structures

Python Lists: Storing and Changing Collections

By Skillancy Editorial Team

Run the code in the free Python Compiler or on your own computer. Lessons that read files use the FreshBasket practice data: download the dataset (ZIP).

So far each variable has held a single value. Real analysis deals with collections: all the items in a cart, the delivery times of a day, the cities FreshBasket serves, the prices of fifty products. Python's most flexible collection is the list, an ordered, changeable sequence of values.

Lists are everywhere. The rows you read from a file arrive as lists, the columns of a pandas DataFrame can be created from lists, and the output of many functions is a list. Understanding them well pays back in every later lesson. This lesson covers how to create lists, read and slice them, change them, sort them, copy them safely, and use the methods that do most of the work.

What is a list?

A list is written with square brackets and commas:

cart = ["Masala Chai 500g", "Paneer 200g", "Banana Robusta"]
prices = [245, 95, 48]
mixed = ["Bengaluru", 560034, 4.5, True]   # allowed, but usually avoid mixing types
empty = []

Lists keep items in order, allow duplicates, and can hold any type, including other lists. Indexing and slicing work exactly like strings: the first item is index 0, -1 is the last, and list[start:stop] excludes stop. The big difference is that you can change a list after creating it.

Worked examples

Example 1: Creating, indexing and slicing

Question: Read parts of FreshBasket's list of eight cities.

cities = ["Bengaluru", "Mumbai", "Delhi", "Hyderabad", "Chennai", "Pune", "Kolkata", "Ahmedabad"]

print(len(cities))
print(cities[0], cities[-1])
print(cities[1:4])
print(cities[:3])
print(cities[-3:])
print(cities[::2])

Output

8
Bengaluru Ahmedabad
['Mumbai', 'Delhi', 'Hyderabad']
['Bengaluru', 'Mumbai', 'Delhi']
['Pune', 'Kolkata', 'Ahmedabad']
['Bengaluru', 'Delhi', 'Chennai', 'Kolkata']

How it works: len counts the items. Positive indexes count from the left starting at 0, negative from the right starting at -1. A slice returns a new list containing the chosen range, and the optional third number is the step.

Reading the result: The list has 8 cities, the first is Bengaluru and the last is Ahmedabad. cities[1:4] is Mumbai to Hyderabad (index 4 is excluded), and cities[::2] picks every second city. Trying cities[8] would raise an IndexError because the last valid index is 7.

Example 2: Lists are mutable

Question: Correct a wrong price and add a new item to a cart.

cart = ["Masala Chai 500g", "Paneer 200g", "Banana Robusta"]
prices = [245, 95, 48]

prices[1] = 98
cart.append("Eggs Tray of 12")
prices.append(96)

print(cart)
print(prices)

Output

['Masala Chai 500g', 'Paneer 200g', 'Banana Robusta', 'Eggs Tray of 12']
[245, 98, 48, 96]

How it works: Assigning to an index replaces that element in place. append adds one item at the end. Unlike strings, lists accept item assignment, which is what mutable means.

Reading the result: The Paneer price changed to 98, and the cart now has 4 items with a matching price list of length 4. Keeping two parallel lists in sync is error-prone; in the dictionaries lesson you will see a better structure.

Example 3: Adding items: append, extend, insert

Question: What is the difference between append, extend and insert?

queue = ["O001", "O002"]

queue.append("O003")
print(queue)

queue.extend(["O004", "O005"])
print(queue)

queue.insert(0, "URGENT")
print(queue)

queue.append(["X1", "X2"])
print(queue)

Output

['O001', 'O002', 'O003']
['O001', 'O002', 'O003', 'O004', 'O005']
['URGENT', 'O001', 'O002', 'O003', 'O004', 'O005']
['URGENT', 'O001', 'O002', 'O003', 'O004', 'O005', ['X1', 'X2']]

How it works: append(x) adds x as one item. extend(iterable) adds each element of the iterable separately. insert(position, x) places x at a position, shifting later items to the right. The last line shows the trap: appending a list adds the whole list as a single nested item.

Reading the result: After extend the queue has five orders, insert put URGENT first, and appending a list created a nested list at the end. Use extend when you want to merge two lists.

Example 4: Removing items: remove, pop, del, clear

Question: How do I take things out of a list?

stock = ["Milk", "Curd", "Butter", "Curd", "Eggs"]

stock.remove("Curd")
print(stock)

last = stock.pop()
print(last, stock)

first = stock.pop(0)
print(first, stock)

del stock[0]
print(stock)

stock.clear()
print(stock)

Output

['Milk', 'Butter', 'Curd', 'Eggs']
Eggs ['Milk', 'Butter', 'Curd']
Milk ['Butter', 'Curd']
['Curd']
[]

How it works: remove(value) deletes the first match and raises ValueError if there is none. pop() removes and returns the last item, and pop(index) removes a specific one. del list[i] deletes by position without returning it, and clear() empties the list.

Reading the result: Only the first Curd was removed, pop() returned Eggs, pop(0) returned Milk, and at the end the list is empty ([]). Popping from an empty list raises IndexError, so check if stock: first.

Example 5: Searching and counting

Question: Where is a payment method in a list, and how often does it occur?

payments = ["UPI", "Card", "UPI", "COD", "UPI", "Wallet"]

print(payments.index("COD"))
print(payments.count("UPI"))
print("Card" in payments, "NetBanking" in payments)
print(payments.index("UPI", 1))

Output

3
3
True False
2

How it works: index(x) returns the position of the first match (and raises ValueError if absent), with an optional starting position. count(x) counts matches, and in answers true or false without raising anything.

Reading the result: COD is at index 3, UPI appears 3 times, and starting the search from index 1 finds the next UPI at index 2. For counting many categories at once, the dictionary and Counter approaches in the next lessons are better.

Example 6: Sorting

Question: Sort delivery times, and sort cities alphabetically, with and without changing the original.

times = [34, 22, 41, 19, 27, 30]

print(sorted(times))
print(times)

times.sort(reverse=True)
print(times)

names = ["pune", "Delhi", "chennai", "Bengaluru"]
print(sorted(names))
print(sorted(names, key=str.lower))

Output

[19, 22, 27, 30, 34, 41]
[34, 22, 41, 19, 27, 30]
[41, 34, 30, 27, 22, 19]
['Bengaluru', 'Delhi', 'chennai', 'pune']
['Bengaluru', 'chennai', 'Delhi', 'pune']

How it works: sorted(list) returns a new sorted list, and the original stays unchanged. The method list.sort() sorts in place and returns None. Both accept reverse=True, and a key function that transforms each item for comparison: str.lower makes the sort case-insensitive. Without it, capitals sort before lowercase letters.

Reading the result: The first line shows ascending times, the second proves the original was untouched, and after sort(reverse=True) the list itself is descending. The plain sorted(names) puts Bengaluru and Delhi before chennai and pune because uppercase comes first, while the case-insensitive version gives a natural order.

Example 7: Built-in functions on lists

Question: Summarise delivery times with len, sum, min, max and an average.

times = [34, 22, 41, 19, 27, 30]

print(len(times), sum(times), min(times), max(times))
print(round(sum(times) / len(times), 2))
print(sorted(times)[len(times) // 2])

Output

6 173 19 41
28.83
30

How it works: These built-ins accept any list of numbers. The average is the sum divided by the count. The last line picks the middle element of the sorted list, an approximation of the median (for an even-length list the exact median averages the two middle values).

Reading the result: We have 6 deliveries totalling 173 minutes, ranging from 19 to 41, with a mean of 28.83. In the statistics lesson you will compute proper medians and spreads.

Example 8: Copying lists safely

Question: Why did changing one list change another?

original = [10, 20, 30]
alias = original
real_copy = original.copy()
also_copy = original[:]

alias.append(40)

print(original)
print(real_copy)
print(also_copy)
print(alias is original, real_copy is original)

Output

[10, 20, 30, 40]
[10, 20, 30]
[10, 20, 30]
True False

How it works: alias = original does not copy anything; it gives the same list a second name. Appending through alias therefore changes what original sees. copy(), the slice [:] and list(original) each create a separate list.

Reading the result: original now has 4 items because the alias modified it, while both copies still have 3. The is check confirms alias and original are the same object. This behaviour is behind a huge number of beginner bugs, especially with functions that modify list arguments.

Example 9: Lists of lists

Question: Represent a small table of stores with their orders and revenue, and read one cell.

table = [
    ["S01", "Bengaluru", 1301],
    ["S03", "Mumbai", 945],
    ["S05", "Delhi", 1291],
]

print(table[1])
print(table[1][1])

for store_id, city, count in table:
    print(f"{store_id} {city:<10} {count:>5}")

table.append(["S08", "Pune", 611])
print(len(table))

Output

['S03', 'Mumbai', 945]
Mumbai
S01 Bengaluru   1301
S03 Mumbai       945
S05 Delhi       1291
4

How it works: Each row is itself a list, so table[1][1] first picks row 1, then column 1 of that row. The for line unpacks each row into three names. Lists of lists are the simplest way to hold tabular data, and pandas generalises the idea into a DataFrame.

Reading the result: table[1] is the Mumbai row and table[1][1] is Mumbai. After appending a row, the table has 4 rows. Beware: [[0] * 3] * 3 creates three references to the same row, a trap for matrix building. Use a comprehension, [[0] * 3 for _ in range(3)], instead.

Example 10: Concatenation, repetition and star unpacking

Question: Combine lists, repeat a pattern and split a list into first, middle and last.

north = ["Delhi", "Pune"]
south = ["Chennai", "Bengaluru"]

all_cities = north + south
print(all_cities)
print(["-"] * 4)

first, *middle, last = all_cities
print(first, middle, last)

head, *tail = [5, 6, 7, 8]
print(head, tail)

Output

['Delhi', 'Pune', 'Chennai', 'Bengaluru']
['-', '-', '-', '-']
Delhi ['Pune', 'Chennai'] Bengaluru
5 [6, 7, 8]

How it works: + joins two lists into a new one, and * with an integer repeats a list. In an assignment, a name with a star collects the leftover items into a list, so first, *middle, last takes the ends and puts everything between them in middle.

Reading the result: The combined list has 4 cities, first is Delhi, last is Bengaluru, and middle is ['Pune', 'Chennai']. Star unpacking is handy for separating a header row from the data rows of a file.

Common mistakes

1. Treating append as extend. a.append([1, 2]) adds one nested item, not two.

2. Expecting sort() to return the sorted list. It returns None.

data = [3, 1, 2]
result = data.sort()
print(result, data)

Output

None [1, 2, 3]

3. Copying with =. Use .copy() or [:] when you need an independent list.

4. Removing items while looping. Loop over a copy or build a new list.

5. Index out of range.

cities = ["Pune", "Delhi"]
print(cities[2])

Output

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    print(cities[2])
          ~~~~~~^^^
IndexError: list index out of range

6. Using list.index for missing values. It raises ValueError, so test with in first.

Practice questions

Practice 1 (Easy): Cart total

Given the prices [245, 95, 48, 96], print the number of items, the total and the most expensive price.

Show solution
prices = [245, 95, 48, 96]
print(len(prices), sum(prices), max(prices))

Output

4 484 245

The three built-ins do the work: 4 items, ₹484 in total, with ₹245 the highest.

Practice 2 (Medium): Clean and rank

From the list [" Pune", "delhi ", "Pune", "MUMBAI", "delhi"], create a cleaned list (stripped, title case), then a list of unique cities in alphabetical order.

Show solution
raw = ["  Pune", "delhi ", "Pune", "MUMBAI", "delhi"]

cleaned = []
for item in raw:
    cleaned.append(item.strip().title())

unique_sorted = sorted(set(cleaned))
print(cleaned)
print(unique_sorted)

Output

['Pune', 'Delhi', 'Pune', 'Mumbai', 'Delhi']
['Delhi', 'Mumbai', 'Pune']

We cleaned each item and appended it. Converting to a set removes duplicates (explained soon), and sorted returns an alphabetical list. The result has 3 cities.

Practice 3 (Hard): Top three without sort

Without using sort or sorted, find the three largest delivery times in [34, 22, 41, 19, 27, 30, 45, 25], largest first.

Show solution
times = [34, 22, 41, 19, 27, 30, 45, 25]
pool = times.copy()
top3 = []

for _ in range(3):
    biggest = max(pool)
    top3.append(biggest)
    pool.remove(biggest)

print(top3)
print(times)

Output

[45, 41, 34]
[34, 22, 41, 19, 27, 30, 45, 25]

Each pass finds the maximum of what is left, records it and removes it from a copy, so the original list stays intact. This costs more than sorting but demonstrates max, remove, copy and a loop working together. The top three are [45, 41, 34].

Key takeaways

  • A list is an ordered, mutable collection written in square brackets; indexing and slicing work like strings.
  • append adds one item, extend adds many, insert places at a position; remove, pop and del take items out.
  • sorted() returns a new list, while .sort() changes the list in place and returns None.
  • b = a creates another name for the same list; use .copy() or [:] for an independent copy.
  • sum, min, max and len summarise numeric lists, and in tests membership.
  • Lists of lists model simple tables, and star unpacking (first, *rest = items) splits a list neatly.

Frequently asked questions

What is the difference between a list and an array?

Python lists hold any mix of types and can grow freely. NumPy arrays (a later module) hold one numeric type and support fast maths on all elements at once, which is what data analysis needs for big data.

How do I remove duplicates from a list?

For unordered results, list(set(items)) works. To keep the original order, use list(dict.fromkeys(items)), which relies on dictionaries preserving insertion order.

How do I check whether a list is empty?

Use if not items: or if len(items) == 0:. The first form is more Pythonic.

Are lists slow for lookups?

Searching with in or index scans the list item by item, so it takes longer on long lists. For fast membership tests on large collections use a set or a dictionary.

Can a list contain itself or other lists?

It can contain other lists to any depth. Nested lists are common for rows of data; just remember that inner lists are shared by reference when copied with a shallow copy, as the next lessons show.

What is the difference between a list and a tuple?

A tuple is like a list that cannot be changed after creation. The next lesson explains when that is useful.

Newsletter

Keep learning Python

One email a week with Python and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.

Ready to practise? Try the Python interview questions