Free Study Material
Every value you've stored so far held one single thing — one number, one string. A list holds many values together, in order, under one single variable name.
A list is an ordered, mutable collection of values, written inside square brackets [], separated by commas. Unlike a string, a list can be changed after creation — items can be added, removed, or modified in place (you met mutability in the Data Types & Mutability topic).
A list can hold values of different types together, and — like a string — its elements are reached using indexing and slicing, using the exact same rules from the Strings chapter.
marks = [78, 85, 92, 67, 74]
mixed = ["Aisha", 16, True]
empty = []
print(marks)
print(marks[0])
print(marks[-1])
print(marks[1:4])
print(type(marks))
marks is a list of five integers. mixed holds a string, an integer, and a bool together — perfectly legal, since a list doesn't require every element to share one type. empty is a valid list with zero elements. marks[0] gives 78 (the first element); marks[-1] gives 74 (the last); marks[1:4] gives [85, 92, 67] — a slice, following the same "stop is excluded" rule as string slicing. type(marks) prints <class 'list'>.
Note: A list is written with square brackets[ ]. Round brackets( )create a different type entirely — a tuple, covered later in this unit — even though both are ordered collections.
Lists exist because most real data naturally comes as a collection, not a single value — a set of five test scores, a set of student names — and a list lets a program work with that entire collection under one name, instead of needing a separate variable for every individual value.
A list is an ordered, mutable collection written in square brackets, able to hold mixed data types, and accessed using the same indexing and slicing rules as strings — marks[0] for a single element, marks[1:4] for a slice, marks[-1] for the last element.
A List is a standard Python data type that stores an ordered sequence of items. It is defined by placing items inside square brackets [ ], separated by commas.
Method 1: Square Brackets (Recommended)
Examples:
empty = []nums = [10, 20, 30]data = ["Amit", 101, 95.5]# Creating a list of Computer Science topics
topics = ["Python", "SQL", "Networking", "Security"]
# 1. Forward Indexing (0, 1, 2, 3)
print("First Topic [0]:", topics[0]) # Output: Python
print("Third Topic [2]:", topics[2]) # Output: Networking
# 2. Backward Indexing (-1, -2...)
# -1 is always the LAST item
print("Last Topic [-1]:", topics[-1]) # Output: Security
print("Second Last [-2]:", topics[-2]) # Output: Networking
list[start : stop : step]
Note: If you omit 'start', it starts at 0. If you omit 'stop', it goes to the end.
# Our list: ["Python", "SQL", "Networking", "Security"]
# Indices: 0 1 2 3
# Slice from index 1 up to (but not including) 3
sub_group = topics[1:3]
print("Sliced List:", sub_group)
# Output: ['SQL', 'Networking']
# Demonstrating Mutability
prices = [100, 200, 300]
print("Before:", prices)
# Change the middle price (Index 1) to 999
prices[1] = 999
print("After:", prices)
# Output: [100, 999, 300]
Lists support the same general operators strings do — concatenation, repetition, membership, length — plus traversal with a for loop. This topic covers all of them for lists specifically.
+) — joins two lists into a new one.*) — repeats a list's elements a given number of times.in / not in) — checks whether a value exists inside a list.for loop.group_a = [1, 2, 3]
group_b = [4, 5]
print(group_a + group_b)
print(group_a * 2)
print(len(group_a))
print(3 in group_a)
print(6 in group_a)
for value in group_a:
print("Value:", value)
group_a + group_b gives [1, 2, 3, 4, 5] — the elements of both lists, joined into a new list. group_a * 2 gives [1, 2, 3, 1, 2, 3] — the elements of group_a repeated twice, not each number doubled. len(group_a) gives 3. 3 in group_a is True; 6 in group_a is False, since 6 is not one of its elements. The for loop then traverses group_a, printing each of its three values on its own line.
Note:group_a 2repeats the whole list's elements — it does not multiply each individual number by2.[1, 2, 3] 2is[1, 2, 3, 1, 2, 3], not[2, 4, 6].
List traversal with for value in group_a: works exactly like string traversal from the Strings chapter — one pass per element, in order — which is why the same loop pattern applies to every collection type in this course, not just lists.
Lists support + (concatenation), * (repetition of the whole list, not its individual values), in/not in (membership), and len(). for element in list: traverses every element in order, one pass each.
The + operator joins two lists end-to-end to create a new list. It does not change the original lists.
The * operator repeats the list items a specific number of times.
# 1. Concatenation (Joining)
# We have two separate teams
team_A = ["Alice", "Bob"]
team_B = ["Charlie", "David"]
# Join them into one big team
all_teams = team_A + team_B
print("Combined:", all_teams)
# Output: ['Alice', 'Bob', 'Charlie', 'David']
# 2. Repetition (Multiplying)
# Create a list of 5 zeros quickly
zeros = [0] * 5
print("Zeros:", zeros)
# Output: [0, 0, 0, 0, 0]
item in list_name
# Shopping List
cart = ["Milk", "Eggs", "Bread"]
# Check if 'Eggs' are in the cart
if "Eggs" in cart:
print("You have Eggs!")
# Check if 'Butter' is missing
if "Butter" not in cart:
print("Warning: You forgot Butter.")
Use this when you just want to read or print the items. It is cleaner and easier to write.
Use this when you need to change the items in the list. You need the index (position number) to update the value.
# Method 1: Direct Access
# Best for printing or reading data
scores = [90, 85, 88]
print("--- Scores ---")
for s in scores:
print(s)
# Method 2: Index Access
# Goal: Add 5 bonus marks to every score
scores = [90, 85, 88]
n = len(scores) # Total items: 3
print("Original:", scores)
# Loop from 0 to 2
for i in range(n):
# Update the item at index 'i'
scores[i] = scores[i] + 5
print("Updated:", scores)
# Output: [95, 90, 93]
A list being mutable means it can actually change after creation — this topic covers the methods that add new elements, and how to overwrite an existing one in place.
value as one new element at the end of the list.value at a specific position, shifting every later element one position to the right.other_list individually onto the end (unlike append(), which would add the whole list as one single element).my_list[i] = new_value, to overwrite the element at that position in place.fruits = ["apple", "banana"]
fruits.append("mango")
print(fruits)
fruits.insert(1, "kiwi")
print(fruits)
fruits.extend(["grape", "fig"])
print(fruits)
fruits[0] = "orange"
print(fruits)
append("mango") adds "mango" at the end: ['apple', 'banana', 'mango']. insert(1, "kiwi") inserts "kiwi" at index 1, pushing "banana" and "mango" one position later: ['apple', 'kiwi', 'banana', 'mango']. extend(["grape", "fig"]) adds both "grape" and "fig" as separate elements at the end: ['apple', 'kiwi', 'banana', 'mango', 'grape', 'fig']. Finally, fruits[0] = "orange" overwrites the element at index 0 in place, changing the list to ['orange', 'kiwi', 'banana', 'mango', 'grape', 'fig'] — the list itself is modified directly; no new list is created.
Warning:fruits.append(["grape", "fig"])(usingappendinstead ofextend) would add the entire two-item list as one single element, makingfruits' length grow by only1, not2—append()never unpacks its argument, butextend()always does.
This append() vs. extend() distinction directly proves lists are mutable in a way strings never can be — fruits[0] = "orange" really does change the original list object in memory, with no new list created, exactly the behaviour the Data Types & Mutability topic promised strings could never have.
append(value) adds one element at the end; insert(index, value) adds one element at a specific position; extend(other_list) adds every element of another list individually at the end; my_list[i] = new_value overwrites an existing element in place — all four change the original list directly, because lists are mutable.
list_name.append(item)
Note: It modifies the list in place and returns None. Do not write L = L.append(x)!
# 1. Using append()
fruits = ["Apple", "Banana"]
print("Original:", fruits)
# Add 'Cherry' to the end
fruits.append("Cherry")
print("After Append:", fruits)
# Output: ['Apple', 'Banana', 'Cherry']
list_name.extend(another_list)
It takes an iterable (like a list or tuple) and appends each element to the list.
# 2. Using extend()
bucket_A = ["Red", "Blue"]
bucket_B = ["Green", "Yellow"]
# Pour bucket_B into bucket_A
bucket_A.extend(bucket_B)
print("Extended List:", bucket_A)
# Output: ['Red', 'Blue', 'Green', 'Yellow']
# Comparison
list_1 = [1, 2]
list_2 = [3, 4]
# If we used APPEND:
# list_1.append(list_2)
# Result: [1, 2, [3, 4]] <- Nested List!
# If we used EXTEND:
# list_1.extend(list_2)
# Result: [1, 2, 3, 4] <- Flat List
list_name.insert(index, item)
# 3. Using insert()
ranks = ["First", "Third"]
# Oops, we missed "Second"!
# Insert "Second" at index 1
ranks.insert(1, "Second")
print("Corrected:", ranks)
# Output: ['First', 'Second', 'Third']
Just as elements can be added, they can be removed — Python gives you four different ways, each suited to a slightly different situation.
value from the list. Raises ValueError if value isn't present at all.index. With no argument, removes and returns the last element.index (a statement, not a method — it doesn't return anything).scores = [55, 70, 70, 90, 40]
scores.remove(70)
print(scores)
last = scores.pop()
print(scores, "| removed:", last)
del scores[0]
print(scores)
scores.clear()
print(scores)
remove(70) deletes only the first 70 it finds, leaving the second one untouched: [55, 70, 90, 40]. pop(), called with no index, removes and returns the last element, 40 — scores becomes [55, 70, 90], and last holds the removed value 40. del scores[0] removes the element at index 0 (55), leaving [70, 90] — del doesn't return the removed value at all. Finally, clear() empties the list entirely, leaving [].
Note:remove()searches by value ("delete this specific value");pop()anddelact by index ("delete whatever is at this position"). Mixing these up is a common source of bugs —scores.remove(0)would try to delete the value0, not the element at index0.
pop() is the one removal method that hands back what it deleted, which makes it the right choice whenever the removed value is still needed afterwards — the other three (remove(), del, clear()) simply discard whatever they remove.
remove(value) deletes the first matching value; pop(index) deletes by position and returns the removed element (last element, if no index given); del list[index] deletes by position without returning anything; clear() empties the list completely.
list_name.remove(value)
Warning: If the value is NOT found, Python raises a ValueError (Crash). Always check with in first!
# 1. Using remove()
cart = ["Milk", "Bread", "Milk", "Butter"]
# We want to remove 'Milk'
# Only the FIRST 'Milk' (Index 0) is gone
cart.remove("Milk")
print("Cart:", cart)
# Output: ['Bread', 'Milk', 'Butter']
variable = list_name.pop(index)
Key Feature: It returns the removed item so you can store it in a variable.
# 2. Using pop()
tasks = ["Homework", "Cleaning", "Cooking"]
# Remove the last item (Cooking) and save it
done_task = tasks.pop()
print("Finished:", done_task) # Output: Cooking
print("Remaining:", tasks) # Output: ['Homework', 'Cleaning']
# Remove specific index (0)
tasks.pop(0)
print("After removing first task:", tasks) # Output: ['Cleaning']
# 3. Using del and clear()
data = [10, 20, 30, 40, 50]
# Delete index 2 (Value 30)
del data[2]
print("After del:", data) # [10, 20, 40, 50]
# Delete a SLICE (Indices 0 to 2)
del data[0:2]
print("After slice del:", data) # [40, 50]
# Clear everything
data.clear()
print("After clear:", data) # []
| Method | Action | Returns Value? |
|---|---|---|
remove(x) |
Deletes first 'x' | No |
pop(i) |
Deletes index 'i' | Yes |
del L[i] |
Deletes index 'i' | No |
Python provides several ready-made functions that work on a whole list at once — summarising it, or putting it in order — without needing a loop written by hand.
Both sort() and sorted() accept reverse=True to sort in descending order instead of ascending.
marks = [67, 92, 45, 88, 71]
print(len(marks))
print(min(marks))
print(max(marks))
print(sum(marks))
marks.sort()
print(marks)
original = [67, 92, 45, 88, 71]
new_list = sorted(original, reverse=True)
print(original)
print(new_list)
len(marks) is 5. min(marks) is 45; max(marks) is 92. sum(marks) is 363. marks.sort() rearranges marks itself, in ascending order: [45, 67, 71, 88, 92] — marks is permanently changed. Separately, sorted(original, reverse=True) returns a brand-new descending list, [92, 88, 71, 67, 45], while original itself is printed completely unchanged: [67, 92, 45, 88, 71].
Warning:marks.sort()returnsNone— writingmarks = marks.sort()is a common mistake that throws away the entire list, replacing it withNone.sort()changes the list itself; it does not hand back a sorted copy.
sort() and sorted() do the same job but disagree on what they touch: sort() is a method that changes its own list and returns nothing, while sorted() is a function that leaves its input list completely alone and hands back a new one — the same "change in place vs. return a new copy" choice you'll see again in later chapters.
len(), min(), max(), and sum() summarise a list. sort() rearranges a list in place and returns nothing; sorted() returns a new sorted list without changing the original — both accept reverse=True for descending order.
len(list)
Returns the total count of items.
sum(list)
Returns the total sum of all numbers.
min(list)
Returns the smallest value.
max(list)
Returns the largest value.
# A list of student marks
marks = [55, 98, 76, 42, 88]
print("Total Students:", len(marks)) # 5
print("Highest Mark:", max(marks)) # 98
print("Lowest Mark:", min(marks)) # 42
print("Total Sum:", sum(marks)) # 359
# Calculate Average (Mean)
average = sum(marks) / len(marks)
print("Average Score:", average) # 71.8
# 1. Reverse
numbers = [1, 2, 3, 4]
numbers.reverse()
print("Reversed:", numbers) # Output: [4, 3, 2, 1]
# 2. Sort (Ascending by default)
data = [10, 5, 20, 1]
data.sort()
print("Sorted:", data) # Output: [1, 5, 10, 20]
# 3. Sort (Descending)
data.sort(reverse=True)
print("Descending:", data) # Output: [20, 10, 5, 1]
If you want to keep the original order but need a sorted version for calculation, use sorted().
If you want to permanently reorder the list, use sort().
# Demonstration: sort() vs sorted()
original = [3, 1, 2]
# Using sorted() - SAFE
new_list = sorted(original)
print("Original stays same:", original) # [3, 1, 2]
print("New List is sorted:", new_list) # [1, 2, 3]
# Using sort() - DESTRUCTIVE
original.sort()
print("Original is changed:", original) # [1, 2, 3]
You've already used in to check whether a value exists in a list. This topic goes further — finding exactly where a value is, and how many times it occurs.
value. Raises ValueError if value isn't present — exactly like the string method of the same name.value occurs in the list.grades = ["B", "A", "C", "A", "B", "A"]
print(grades.index("A"))
print(grades.count("A"))
print(grades.count("D"))
grades.index("A") returns 1 — the position of the first "A", even though "A" appears again later at positions 3 and 5. grades.count("A") returns 3, since "A" occurs three times in total. grades.count("D") returns 0 — count() never raises an error for a value that isn't present; it simply reports zero occurrences.
Note:count()returning0for a missing value andindex()raising aValueErrorfor one are two different, deliberate behaviours — checkingvalue in my_listfirst is the safe way to callindex()only when you already know it will succeed.
index() and count() complement each other: index() answers "where is it, at minimum?" while count() answers "how many are there, in total?" — searching a list for a specific position and tallying how often a value repeats are two genuinely different questions, even though both start by scanning the same list.
index(value) returns the position of a value's first occurrence, raising ValueError if it's missing; count(value) returns how many times a value occurs, returning 0 (never an error) if it's missing.
list_name.index(item)
Critical Warning: If the item is NOT found, Python raises a ValueError (Crash). Always check if the item exists using in before asking for its index!
# 1. Using index()
grades = ["A", "B", "A", "C", "A"]
# Find where the FIRST "B" is
pos = grades.index("B")
print("B is at index:", pos) # Output: 1
# Find where the FIRST "A" is
# Notice it stops at index 0 and ignores the rest
print("First A is at:", grades.index("A")) # Output: 0
list_name.count(item)
# 2. Using count()
grades = ["A", "B", "A", "C", "A"]
# How many A's?
total_As = grades.count("A")
print("Total A grades:", total_As) # Output: 3
# How many F's? (Not in list)
print("Total F grades:", grades.count("F")) # Output: 0
A list's elements can themselves be lists — this is how Python represents grid-like or table-like data, such as rows of student records. This closing topic covers reading nested lists, then puts everything from this chapter to work in two complete programs.
A nested list is a list containing other lists as elements. Reaching an element inside an inner list needs two indexes: the first selects which inner list, the second selects the position inside it — outer[i][j].
students = [
["Aisha", 91],
["Rahul", 78],
["Meera", 85]
]
print(students[0])
print(students[0][0])
print(students[1][1])
for record in students:
print(record[0], "scored", record[1])
students[0] gives the entire first inner list, ['Aisha', 91]. students[0][0] reaches inside that inner list to its first element, 'Aisha'. students[1][1] reaches the second inner list's second element, 78. The for record in students: loop traverses the outer list one row at a time — each record is itself a two-item inner list, so record[0] and record[1] inside the loop reach the name and the score for that row.
students = [
["Aisha", 91],
["Rahul", 78],
["Meera", 85]
]
topper = students[0]
for record in students:
if record[1] > topper[1]:
topper = record
print("Topper:", topper[0], "with", topper[1], "marks")
topper starts as the first record and is replaced whenever the loop finds a record with a higher score at record[1] than topper[1]'s current value. By the end, topper holds ['Aisha', 91] — the highest-scoring record — so the program prints Topper: Aisha with 91 marks.
Note:topper = students[0]doesn't copy the inner list —topperandstudents[0]both point at the same list, since lists are mutable. That distinction matters once modifying one of them is involved, but doesn't affect this program, which only reads values.
This "start with the first item, then replace it whenever something better is found" pattern is the standard way to find a maximum by hand, without using the max() function — the same running-comparison idea from the Iterative Statements chapter's practice programs, applied here to nested data instead of single numbers.
A nested list is a list of lists, read using two indexes (outer[i][j]) — the outer index selects a row, the inner index selects a position within it. Traversing a nested list with for record in outer_list: visits one inner list per pass, which is the standard way to process table-like data such as a list of student records.
Example Matrix:
| Col 0 | Col 1 | |
| Row 0 | 10 | 20 |
| Row 1 | 30 | 40 |
To access 30: matrix[1][0]
# Creating a 3x3 Matrix
matrix = [
[1, 2, 3], # Row 0
[4, 5, 6], # Row 1
[7, 8, 9] # Row 2
]
# Accessing elements
print("Row 1, Col 2:", matrix[1][2]) # Output: 6
print("Row 0, Col 0:", matrix[0][0]) # Output: 1
# Modifying an element
matrix[2][0] = 99
print("Modified Row 2:", matrix[2]) # Output: [99, 8, 9]
# Program: Linear Search
numbers = [10, 50, 30, 70, 80, 20]
target = 70
found = False
for i in range(len(numbers)):
if numbers[i] == target:
print("Found", target, "at Index:", i)
found = True
break # Stop searching once found
if found == False:
print(target, "not found in list.")
# Program: Count Frequency
data = [1, 2, 1, 3, 1, 4, 1]
search_item = 1
count = 0
for item in data:
if item == search_item:
count = count + 1
print("Number", search_item, "appears", count, "times.")
# Program: Find Max, Min, and Mean manually
values = [45, 88, 12, 67, 99, 34]
# Initialize variables with the first item
max_val = values[0]
min_val = values[0]
total_sum = 0
for x in values:
# Check Max
if x > max_val:
max_val = x
# Check Min
if x < min_val:
min_val = x
# Add to Total
total_sum = total_sum + x
mean_val = total_sum / len(values)
print("Maximum:", max_val)
print("Minimum:", min_val)
print("Mean (Average):", mean_val)
A working recap of this chapter, tying together creating, changing, searching, and sorting a list in one continuous program.
[value1, value2, ...] — an ordered, mutable collection.my_list[i]) and slicing (my_list[a:b]), same rules as strings.append() (one value, at the end), insert() (one value, at a position), extend() (many values, at the end).remove() (by value), pop()/del (by index), clear() (everything).len(), min(), max(), sum(), count(), index(), in.sort() (in place), sorted() (returns a new list).scores = [70, 85, 60]
scores.append(95)
scores.insert(0, 40)
print("After adding:", scores)
scores.remove(60)
print("After removing 60:", scores)
print("Total scores:", len(scores))
print("Highest score:", max(scores))
print("Does 85 exist?", 85 in scores)
scores.sort()
print("Sorted ascending:", scores)
scores.sort(reverse=True)
print("Sorted descending:", scores)
Starting from [70, 85, 60]: append(95) adds it at the end, then insert(0, 40) adds 40 at the very start, giving [40, 70, 85, 60, 95]. remove(60) deletes that one value, leaving [40, 70, 85, 95]. len(scores) is now 4; max(scores) is 95; 85 in scores is True. sort() rearranges the list ascending, [40, 70, 85, 95], and sort(reverse=True) then rearranges that same list descending, [95, 85, 70, 40] — both change scores itself, in place, one after the other.
Tip: Every method used above either changes the list in place and returns nothing (append,insert,remove,sort) or leaves it untouched and returns a new value (len,max,in,sorted) — knowing which group a method belongs to is the single most useful thing to check before using its result.
A list is created with [ ], read with indexing/slicing, grown with append()/insert()/extend(), shrunk with remove()/pop()/del/clear(), inspected with len()/min()/max()/sum()/count()/index()/in, and ordered with sort() (in place) or sorted() (a new list) — the full toolkit this chapter covered, working together on one list.
A List is an ordered collection of items enclosed in [ ]. It acts as a container that can hold mixed data types.
You CAN change, add, or delete items after creation.
Items stay in the order you place them (0, 1, 2...).
| Category | Method / Function | Action |
|---|---|---|
| Adding | L.append(x) |
Adds x to the END. |
L.extend(seq) |
Adds multiple items from seq to the END. | |
L.insert(i, x) |
Inserts x at index i. | |
| Deleting | L.remove(x) |
Removes first occurrence of value x. |
L.pop(i) |
Removes & returns item at index i (Default: Last). | |
del L[i] |
Deletes item at index i (No return). | |
L.clear() |
Removes ALL items. | |
| Search | L.index(x) |
Returns index of first x. (Error if not found). |
L.count(x) |
Counts how many times x appears. | |
| Order | L.sort() |
Sorts list IN-PLACE. |
L.reverse() |
Reverses list IN-PLACE. | |
sorted(L) |
Returns a NEW sorted list. | |
| Stats | len(L) |
Total number of items. |
sum(L) |
Sum of all numbers. | |
max(L) |
Largest value. | |
min(L) |
Smallest value. |
# Read-Only (Cleaner)
for item in L:
print(item)
# Read & Write (If you need to change items)
for i in range(len(L)):
L[i] = L[i] + 1