Lists are one of the most important data structures in Python. They let you store multiple values in a single variable, keep those values in order, and easily add, remove, and update items as your program runs. If you already understand strings, you’ll feel at home with lists—because many of the same ideas (like indexing and slicing) work in almost the same way.
A list is an ordered collection of values. You create a list using square brackets [], and items are separated by commas.
Example:
my_list = [1, 2, 3, 4, 5]
print(my_list)Lists can store any type of data: numbers, strings, booleans, even other lists.
Example :
mixed = [10, "Python", True, 3.14]
print(mixed)
[Creating and printing a list]
Lists are zero-indexed, meaning the first item is at index 0.
Example :
my_list = ["one", "two", "three", "four", "five"]
print(my_list[0]) # one
print(my_list[3]) # four
You can also use negative indexes to count from the end.
Example:
print(my_list[-1]) # five
print(my_list[-2]) # four[ Indexing with positive and negative indexes]
Slicing lets you extract a sub-list. The basic slice format is:
my_list[start:end]start is includedend is excludedExample :
my_list = ["one", "two", "three", "four", "five"]
print(my_list[2:]) # from index 2 to the end
print(my_list[1:4]) # indexes 1, 2, 3
This works just like slicing strings, which is why lists and strings often feel similar in Python.
[ Slicing output examples]
A powerful extension of slicing is the step value:
my_list[start:end:step]The step controls how many positions to jump each time.
Example:
my_list = ["one", "two", "three", "four", "five", "six"]
print(my_list[0:6:2]) # every 2nd item
print(my_list[0:6:3]) # every 3rd item
If you’re slicing from the beginning to the end, you can omit start and end.
Example:
print(my_list[::2])This reads as: “from start to end, take every 2nd item.”
[Step slicing with different step values]
Typing long lists by hand is boring and error-prone. Python gives you the range() function to generate sequences of numbers.
Example:
for i in range(5):
print(i)range() is a sequence type that is commonly used in loops. When you need an actual list, you can convert it using list().
Example:
numbers = list(range(10))
print(numbers)Now you can slice it like any other list.
Example :
numbers = list(range(100))
print(numbers[::2]) # every other number
print(numbers[::5]) # every 5th number
print(numbers[::10]) # every 10th number[Creating a long list with range and slicing it]
A negative step means “move backwards.” This is an extremely handy trick.
Example:
numbers = list(range(20))
print(numbers[::-1]) # reversed list
print(numbers[::-2]) # reversed, every other item
print(numbers[::-10]) # reversed, jump by 10
This concept is worth remembering because it shows up everywhere in real-world Python.
[Negative step slicing examples]
So far, slicing and indexing have been about reading data. Now let’s update a list.
append()Example:
my_list = [1, 2, 3, 4]<br>my_list.append(5)<br>print(my_list)append() adds a single item to the end of the list.
insert()Example :
my_list = [1, 2, 3, 4]
my_list.insert(2, 99) # insert 99 at index 2
print(my_list)
insert(index, value) shifts items to the right to make room.
[ append vs insert output]
remove() and pop()Python gives you multiple ways to remove list elements. Which one you choose depends on what you have: a value or an index.
remove()Example:
my_list = [1, 2, 3, 2, 4]
my_list.remove(2)
print(my_list) # removes the first 2remove(value) deletes the first matching item.
One important warning: if the value is not in the list, Python raises an error.
my_list = [1, 2, 3]
# my_list.remove(99) # ValueError: list.remove(x): x not in list
In real programs, you often protect this with a check.
if 99 in my_list:
my_list.remove(99)
pop()pop() removes an item and returns it.
my_list = [1, 2, 3, 4, 5]
last_item = my_list.pop()
print(last_item) # 5
print(my_list) # [1, 2, 3, 4]You can also pop a specific index.
my_list = ["a", "b", "c", "d"]
item = my_list.pop(1)
print(item) # b
print(my_list) # ['a', 'c', 'd']
[ remove vs pop output]
Sometimes you want to repeatedly remove items until the list becomes empty. A very clean pattern is:
my_list = [1, 2, 3, 4]
while len(my_list):
print(my_list.pop())
print(my_list) # []
The key idea here is that len(my_list) becomes 0 when the list is empty, and 0 behaves like False in a condition—so the loop stops automatically.
[while loop popping items until list is empty]
This is a topic that surprises many beginners, but it’s essential if you want to avoid confusing bugs.
Consider this code:
a = [1, 2, 3, 4, 5]
b = a
a.append(6)
print(b)Even though you modified a, the change appears in b. That happens because b = a does not create a new list—it creates a new reference to the same list in memory.
copy()If you want b to be a separate list stored independently, make a copy.
a = [1, 2, 3, 4, 5]
b = a.copy()
a.append(6)
print("a:", a)
print("b:", b)Now a changes, but b stays the same—because it’s a different list in memory.
[Difference between assignment and copy]
Open a Python notebook (or a .py file) and experiment with these ideas. The goal is to build intuition.
list(range(20)).append() and insert().remove() and remove by index using pop().copy() and compare the results.[Your exercise outputs]
By now, you should be comfortable with what makes lists so useful in Python. You learned how slicing works (including steps and reverse slicing), how to generate large lists quickly using range(), how to modify a list with append() and insert(), and how to remove items safely using remove() and pop(). Most importantly, you saw why list assignment and list copying behave differently—an idea that becomes increasingly important as your programs grow.