Python Lists
Lists
A list is a built-in data structure used to store multiple values in a single variable. Instead of creating separate variables such as item1, item2, and item3, you can store all the items together in one list and work with them as a group.
Lists are one of the most frequently used data types in Python. Shopping carts, to-do items, rows from a file, search results, and student marks are all naturally represented as lists.
Creating a List
Lists are created with square brackets [ ], with items separated by commas.
items = ["pen", "notebook", "eraser"]
print(items)
Expected output:
['pen', 'notebook', 'eraser']
An empty list is written as []:
cart = []
print(cart)
Expected output:
[]
Lists Among Python's Collection Types
Python has four core built-in collection data types. Each is designed for a different use case:
| Collection | Ordered | Changeable | Duplicates Allowed | Syntax |
|---|---|---|---|---|
| List | Yes | Yes | Yes | ["a", "b"] |
| Tuple | Yes | No | Yes | ("a", "b") |
| Set | No | Items cannot be changed, but items can be added or removed | No | {"a", "b"} |
| Dictionary | Yes (Python 3.7+) | Yes | No duplicate keys | {"key": "value"} |
Choose a list when you need an ordered collection that you may need to change.
List Characteristics
1. Ordered
Lists keep their items in a defined order. Items appear in the same sequence in which they were added, and that order does not change unless you change it.
colors = ["red", "green", "blue"]
print(colors)
Expected output:
['red', 'green', 'blue']
New items are added at the end unless you specify a different position.
2. Changeable (Mutable)
Lists are mutable: you can add, update, and remove items after the list is created. The list is modified in place — no new list is created.
numbers = [2, 4, 6]
numbers[1] = 10
print(numbers)
Expected output:
[2, 10, 6]
The item at index 1 (4) was replaced with 10. Compare this with strings, which cannot be changed this way.
3. Allows Duplicate Values
Lists can contain the same value more than once, because each item is identified by its position, not its value.
names = ["John", "Emma", "John", "Alex"]
print(names)
Expected output:
['John', 'Emma', 'John', 'Alex']
Indexing in Lists
Each item in a list has a position called an index. Indexing starts at 0.
languages = ["Python", "Java", "C++"]
print(languages[0])
print(languages[2])
Expected output:
Python
C++
| Item | "Python" | "Java" | "C++" |
|---|---|---|---|
| Index | 0 | 1 | 2 |
| Negative index | -3 | -2 | -1 |
Finding the Length of a List
Use len() to find how many items a list contains.
cities = ["Delhi", "Mumbai", "Chennai"]
print(len(cities))
Expected output:
3
The last valid index is always len(list) - 1.
List Items and Data Types
A list can store items of any data type.
Items of the Same Type
scores = [85, 90, 78, 92]
flags = [True, False, True]
Items of Mixed Types
profile = ["Alex", 25, True, 5.8]
print(profile)
Expected output:
['Alex', 25, True, 5.8]
Mixed lists are valid, but in practice lists usually hold items of the same kind. For a record with named fields (name, age, status), a dictionary is often clearer.
A list can even contain other lists, which is called a nested list:
matrix = [[1, 2], [3, 4]]
print(matrix[1][0])
Expected output:
3
matrix[1] is the inner list [3, 4], and [0] selects its first item.
Checking the Data Type of a List
Lists are objects of the class list.
data = ["car", "bike", "bus"]
print(type(data))
Expected output:
<class 'list'>
Creating a List With the list() Constructor
The list() constructor creates a list from any iterable — any object that can be looped over, such as a tuple, string, set, or range.
fruits = list(("mango", "orange", "grapes"))
print(fruits)
Expected output:
['mango', 'orange', 'grapes']
Note: Double parentheses are used because the inner pair creates a tuple, which is then passed to
list().
More examples:
print(list("abc"))
print(list(range(5)))
Expected output:
['a', 'b', 'c']
[0, 1, 2, 3, 4]
Accessing List Items
Accessing Items by Index
fruits = ["mango", "orange", "grapes"]
print(fruits[1])
Expected output:
orange
Index 1 refers to the second item, because Python uses zero-based indexing.
Accessing an index that does not exist raises an error:
fruits = ["mango", "orange", "grapes"]
print(fruits[3])
Output:
IndexError: list index out of range
Negative Indexing
Negative indexes access items from the end of the list:
-1→ the last item-2→ the second-to-last item
fruits = ["mango", "orange", "grapes"]
print(fruits[-1])
Expected output:
grapes
Negative indexing is convenient because you do not need to know the list's length to get the last item.
Accessing a Range of Items (Slicing)
Use slicing to retrieve several items at once. The syntax is list[start:end].
items = ["pen", "pencil", "eraser", "scale", "marker", "sharpener"]
print(items[2:5])
Expected output:
['eraser', 'scale', 'marker']
- The slice starts at index 2 (included).
- It stops at index 5 (excluded).
- It returns a new list; the original list is unchanged.
Omitting the Start Index
items = ["pen", "pencil", "eraser", "scale", "marker"]
print(items[:3])
Expected output:
['pen', 'pencil', 'eraser']
This returns the items from the beginning up to, but not including, index 3.
Omitting the End Index
items = ["pen", "pencil", "eraser", "scale", "marker"]
print(items[1:])
Expected output:
['pencil', 'eraser', 'scale', 'marker']
Slicing continues to the end of the list.
Slicing With Negative Indexes
items = ["pen", "pencil", "eraser", "scale", "marker", "sharpener"]
print(items[-4:-1])
Expected output:
['eraser', 'scale', 'marker']
- The slice starts at the fourth item from the end (
"eraser"). - It stops before the last item (
"sharpener").
Checking Whether an Item Exists
Use the in keyword to check whether a value is in a list.
colors = ["red", "blue", "green"]
if "blue" in colors:
print("Yes, 'blue' is available in the list")
Expected output:
Yes, 'blue' is available in the list
The condition is True when the item is present. This pattern is common in validation and conditional logic.
Changing List Items
Because lists are mutable, you can update individual items, replace several items at once, or insert new items at a specific position.
Changing a Single Item
Access the item by its index and assign a new value.
fruits = ["mango", "orange", "grapes"]
fruits[1] = "pineapple"
print(fruits)
Expected output:
['mango', 'pineapple', 'grapes']
The old value at index 1 is replaced with the new one.
Changing Multiple Items Using a Slice
Assign a new list to a slice to replace a group of items.
fruits = ["mango", "orange", "grapes", "papaya", "apple"]
fruits[1:3] = ["kiwi", "strawberry"]
print(fruits)
Expected output:
['mango', 'kiwi', 'strawberry', 'papaya', 'apple']
The items at indexes 1 and 2 are replaced. Two items were removed and two were inserted, so the length stays the same.
Replacing With More Items Than Removed
If you insert more items than you replace, the list grows.
fruits = ["mango", "orange", "grapes"]
fruits[1:2] = ["kiwi", "strawberry"]
print(fruits)
Expected output:
['mango', 'kiwi', 'strawberry', 'grapes']
One item ("orange") is replaced with two new items, so the list length grows from 3 to 4.
Replacing With Fewer Items Than Removed
If you insert fewer items than you replace, the list shrinks.
fruits = ["mango", "orange", "grapes"]
fruits[1:3] = ["banana"]
print(fruits)
Expected output:
['mango', 'banana']
Two items are replaced with one, so the list becomes shorter.
Note: The list's length changes whenever the number of inserted items differs from the number of replaced items.
Inserting an Item Without Replacing
To add an item at a specific position without removing anything, use insert().
fruits = ["mango", "orange", "grapes"]
fruits.insert(2, "apple")
print(fruits)
Expected output:
['mango', 'orange', 'apple', 'grapes']
The new item is placed at index 2, and the existing items from that position onward shift one place to the right.
Adding Items to a List
Adding an Item With append()
append() adds one item to the end of the list.
colors = ["red", "blue", "green"]
colors.append("yellow")
print(colors)
Expected output:
['red', 'blue', 'green', 'yellow']
The original list is modified directly.
Inserting an Item With insert()
insert(index, item) adds an item at a chosen position.
colors = ["red", "blue", "green"]
colors.insert(1, "orange")
print(colors)
Expected output:
['red', 'orange', 'blue', 'green']
After append() or insert(), the list's length increases by one.
Extending a List With Another List
extend() adds all items from another list, one by one, to the end of the list.
primary = ["pen", "pencil", "eraser"]
stationery = ["marker", "scale", "sharpener"]
primary.extend(stationery)
print(primary)
Expected output:
['pen', 'pencil', 'eraser', 'marker', 'scale', 'sharpener']
Extending With Any Iterable
extend() works with any iterable — tuples, sets, dictionaries (which add their keys), and even strings.
tools = ["hammer", "screwdriver"]
extras = ("wrench", "pliers")
tools.extend(extras)
print(tools)
Expected output:
['hammer', 'screwdriver', 'wrench', 'pliers']
The items of the tuple are added individually.
append() vs extend()
This difference is a frequent source of bugs:
a = ["pen"]
a.append(["ink", "nib"])
print(a)
b = ["pen"]
b.extend(["ink", "nib"])
print(b)
Expected output:
['pen', ['ink', 'nib']]
['pen', 'ink', 'nib']
append()adds the whole list as one item, creating a nested list.extend()adds each item separately.
| Method | Adds | Position |
|---|---|---|
append() | One item | End of the list |
insert() | One item | Specific index |
extend() | Every item from an iterable | End of the list |
Removing List Items
Python offers several ways to remove items, depending on whether you know the value, the index, or want to remove everything.
Removing an Item by Value With remove()
remove() deletes the first occurrence of a value.
fruits = ["mango", "banana", "apple"]
fruits.remove("banana")
print(fruits)
Expected output:
['mango', 'apple']
If the value does not exist, Python raises an error:
ValueError: list.remove(x): x not in list
To avoid this, check first: if "banana" in fruits: fruits.remove("banana").
Removing Only the First Match
When the list contains duplicates, remove() deletes only the first one.
fruits = ["mango", "banana", "apple", "banana", "kiwi"]
fruits.remove("banana")
print(fruits)
Expected output:
['mango', 'apple', 'banana', 'kiwi']
The second "banana" stays in the list.
Removing an Item by Index With pop()
pop(index) removes the item at the given index and returns it.
colors = ["red", "blue", "green"]
removed = colors.pop(1)
print(removed)
print(colors)
Expected output:
blue
['red', 'green']
Returning the removed value is useful when you need to process it — for example, taking the next task from a queue.
Removing the Last Item
Without an index, pop() removes and returns the last item.
colors = ["red", "blue", "green"]
colors.pop()
print(colors)
Expected output:
['red', 'blue']
Removing an Item With del
The del keyword removes the item at a specific index.
numbers = [10, 20, 30]
del numbers[0]
print(numbers)
Expected output:
[20, 30]
Unlike pop(), del does not return the removed value. It can also remove a slice: del numbers[0:2].
Deleting the Entire List
del can also delete the list variable itself.
items = ["pen", "pencil", "eraser"]
del items
print(items)
Output:
NameError: name 'items' is not defined
After deletion, the name items no longer exists.
Clearing All Items With clear()
clear() removes all items but keeps the list object.
tasks = ["email", "meeting", "report"]
tasks.clear()
print(tasks)
Expected output:
[]
The list is now empty, but the variable still exists and can be reused.
| Tool | Removes | Returns the Removed Item |
|---|---|---|
remove(value) | First matching value | No |
pop(index) | Item at index (last item by default) | Yes |
del list[index] | Item at index or a slice | No |
clear() | All items | No |
Looping Through a List
Looping With a for Loop
The most common and readable way to process each item:
fruits = ["mango", "orange", "grapes"]
for item in fruits:
print(item)
Expected output:
mango
orange
grapes
On each pass, item holds the next item in the list. Use this approach when you do not need index numbers.
Looping Using Index Numbers
If you need the index, loop over range(len(...)):
fruits = ["mango", "orange", "grapes"]
for index in range(len(fruits)):
print(f"Index {index}:", fruits[index])
Expected output:
Index 0: mango
Index 1: orange
Index 2: grapes
len(fruits)is3.range(3)produces0,1,2— exactly the valid indexes.
A more Pythonic way to get both the index and the item is enumerate():
fruits = ["mango", "orange", "grapes"]
for index, item in enumerate(fruits):
print(f"Index {index}:", item)
The output is the same as the previous example.
Looping With a while Loop
A while loop can also iterate through a list using an index variable.
fruits = ["mango", "orange", "grapes"]
counter = 0
while counter < len(fruits):
print(fruits[counter])
counter += 1
Expected output:
mango
orange
grapes
The loop continues until counter reaches the list's length. You must increase the index yourself; forgetting counter += 1 creates an infinite loop.
A Note on Comprehensions for Side Effects
You may see code like this:
[print(item) for item in fruits]
It works, but it builds a throwaway list of None values just to call print(). List comprehensions are meant for creating lists, not for performing actions. Use a regular for loop for printing and other side effects.
Choosing a Looping Method
| Method | Best Use Case |
|---|---|
for item in list | Simple iteration over items |
for index, item in enumerate(list) | When you need both the index and the item |
while loop | When iteration depends on a condition other than reaching the end |
| List comprehension | When creating a new list from an existing one |
List Comprehension
List comprehension is a concise way to create a new list from an existing iterable. It combines a loop, an optional condition, and an expression into a single readable line.
Why Use It?
Suppose you have a list of fruits and want a new list containing only the fruits whose names include the letter "e".
Traditional Approach
items = ["grape", "melon", "plum", "berry", "fig"]
filtered_items = []
for fruit in items:
if "e" in fruit:
filtered_items.append(fruit)
print(filtered_items)
Using List Comprehension
items = ["grape", "melon", "plum", "berry", "fig"]
filtered_items = [fruit for fruit in items if "e" in fruit]
print(filtered_items)
Expected output (both versions):
['grape', 'melon', 'berry']
The single comprehension line replaces the empty list, the loop, the condition, and the append() call.
General Syntax
new_list = [expression for element in iterable if condition]
| Part | Meaning |
|---|---|
expression | What is added to the new list (often the element itself, or a transformed version of it) |
element | A variable that holds each item from the iterable |
iterable | Any sequence or collection: a list, tuple, set, string, or range |
if condition | Optional filter; only items for which it is True are included |
The original iterable is not changed; a new list is returned.
Using a Condition as a Filter
colors = ["red", "blue", "green", "red", "yellow"]
result = [c for c in colors if c != "red"]
print(result)
Expected output:
['blue', 'green', 'yellow']
Every value except "red" is kept.
Without a Condition
If no filtering is needed, omit the condition:
numbers = [1, 2, 3, 4, 5]
copy_list = [n for n in numbers]
print(copy_list)
Expected output:
[1, 2, 3, 4, 5]
Using range() as the Iterable
squares = [n * n for n in range(1, 6)]
print(squares)
Expected output:
[1, 4, 9, 16, 25]
With a condition:
even_numbers = [n for n in range(1, 11) if n % 2 == 0]
print(even_numbers)
Expected output:
[2, 4, 6, 8, 10]
Transforming Values With the Expression
The expression can transform each item.
names = ["alice", "bob", "charlie"]
upper_names = [name.upper() for name in names]
print(upper_names)
Expected output:
['ALICE', 'BOB', 'CHARLIE']
The expression can also be a fixed value:
status = ["active" for _ in range(5)]
print(status)
Expected output:
['active', 'active', 'active', 'active', 'active']
The underscore _ is a conventional name for a loop variable whose value is not used.
Conditional Expressions Inside a Comprehension
To change values rather than filter them, put an if–else expression at the start:
fruits = ["apple", "banana", "cherry"]
updated_fruits = [fruit if fruit != "banana" else "orange" for fruit in fruits]
print(updated_fruits)
Expected output:
['apple', 'orange', 'cherry']
"banana" is replaced with "orange"; other values stay the same.
Position matters: An
ifat the end filters items out. Anif–elseat the start decides what value each item becomes.
Sorting Lists
Sorting With sort()
sort() arranges the list's items in ascending order, modifying the list in place.
numbers = [42, 7, 19, 3]
numbers.sort()
print(numbers)
words = ["mango", "apple", "kiwi"]
words.sort()
print(words)
Expected output:
[3, 7, 19, 42]
['apple', 'kiwi', 'mango']
To sort in descending order, use reverse=True:
numbers = [42, 7, 19, 3]
numbers.sort(reverse=True)
print(numbers)
Expected output:
[42, 19, 7, 3]
sort() vs sorted()
sort() changes the list and returns None. The built-in sorted() function returns a new sorted list and leaves the original unchanged.
numbers = [3, 1, 2]
ordered = sorted(numbers)
print(ordered)
print(numbers)
Expected output:
[1, 2, 3]
[3, 1, 2]
A common mistake is writing numbers = numbers.sort(), which sets numbers to None.
Case-Insensitive Sorting
By default, strings are sorted case-sensitively, by character code. All uppercase letters come before all lowercase letters, which can produce unexpected results.
items = ["banana", "Cherry", "apple", "Mango"]
items.sort()
print(items)
Expected output:
['Cherry', 'Mango', 'apple', 'banana']
The capitalized words come first, even though "apple" should come first alphabetically.
Using a Key Function
The key parameter accepts a function that transforms each item before comparison. Passing str.lower compares every item in lowercase form, while keeping the original values in the list.
items = ["banana", "Cherry", "apple", "Mango"]
items.sort(key=str.lower)
print(items)
Expected output:
['apple', 'banana', 'Cherry', 'Mango']
This produces natural alphabetical order regardless of capitalization.
Reversing a List
reverse() flips the current order of the items. It does not sort them.
items = ["grapes", "Apple", "mango", "Cherry"]
items.reverse()
print(items)
Expected output:
['Cherry', 'mango', 'Apple', 'grapes']
Sorting and Reversing Together
To sort case-insensitively in descending order:
items = ["grapes", "Apple", "mango", "Cherry"]
items.sort(key=str.lower, reverse=True)
print(items)
Expected output:
['mango', 'grapes', 'Cherry', 'Apple']
Copying a List
Assigning one list to another variable with = does not create a new list. Both variables refer to the same list object, so a change made through one variable is visible through the other.
Why Direct Assignment Is a Problem
list_a = ["pen", "pencil", "eraser"]
list_b = list_a
list_a.append("marker")
print(list_b)
Expected output:
['pen', 'pencil', 'eraser', 'marker']
list_b shows "marker" even though only list_a was changed, because there is only one list with two names.
Correct Ways to Copy a List
1. Using copy()
original = ["pen", "pencil", "eraser"]
duplicate = original.copy()
original.append("marker")
print(original)
print(duplicate)
Expected output:
['pen', 'pencil', 'eraser', 'marker']
['pen', 'pencil', 'eraser']
duplicate is a separate list, so changes to original do not affect it.
2. Using the list() Constructor
original = ["pen", "pencil", "eraser"]
duplicate = list(original)
print(duplicate)
Expected output:
['pen', 'pencil', 'eraser']
This works the same way as copy().
3. Using the Slice Operator
Slicing the whole list with [:] also creates a new list.
original = ["pen", "pencil", "eraser"]
duplicate = original[:]
print(duplicate)
Expected output:
['pen', 'pencil', 'eraser']
Shallow Copies and Nested Lists
All three methods create a shallow copy: the outer list is new, but any inner lists are still shared.
original = [[1, 2], [3, 4]]
duplicate = original.copy()
duplicate[0].append(99)
print(original)
Expected output:
[[1, 2, 99], [3, 4]]
For lists that contain other lists, use copy.deepcopy() from the copy module to copy everything:
import copy
original = [[1, 2], [3, 4]]
duplicate = copy.deepcopy(original)
duplicate[0].append(99)
print(original)
Expected output:
[[1, 2], [3, 4]]
Joining (Concatenating) Lists
You can combine lists in several ways, depending on whether you want a new list or want to modify an existing one.
1. Using the + Operator
+ joins lists and returns a new list, leaving the originals unchanged.
letters = ["x", "y", "z"]
numbers = [10, 20, 30]
combined = letters + numbers
print(combined)
Expected output:
['x', 'y', 'z', 10, 20, 30]
Best when you want a new list without altering the originals.
2. Appending Items One by One
Use a loop with append() to add items from one list to another.
letters = ["x", "y", "z"]
numbers = [10, 20, 30]
for value in numbers:
letters.append(value)
print(letters)
Expected output:
['x', 'y', 'z', 10, 20, 30]
Useful when you need more control — for example, to skip or transform some items while adding them.
3. Using extend()
extend() adds all items from one list to the end of another in a single step.
letters = ["x", "y", "z"]
numbers = [10, 20, 30]
letters.extend(numbers)
print(letters)
Expected output:
['x', 'y', 'z', 10, 20, 30]
letters is modified in place; no new list is created.
Common Mistakes
| Mistake | Example | Problem |
|---|---|---|
| Index out of range | items[len(items)] | IndexError; the last index is len(items) - 1 |
Assigning the result of sort() | items = items.sort() | items becomes None |
Copying with = | b = a | Both names share one list |
Using append() to add several items | a.append([1, 2]) | Creates a nested list; use extend() |
| Removing a missing value | items.remove("x") | ValueError |
| Removing items while looping over the same list | for x in items: items.remove(x) | Items are skipped; loop over a copy instead |
Related Concepts
- Python List Methods — a complete reference of list methods
- Python Tuples — ordered collections that cannot be changed
- Python Sets — collections of unique items
- Python For Loops — iterating over lists and other sequences