Lists and Strings
Holding many values
A variable holds one value. When you need thirty students' marks, thirty variables is not a workable answer. A list holds an ordered collection of values under a single name.
marks = [78, 65, 91, 45, 88]
subjects = ["Maths", "Physics", "Chemistry"]
mixed = [1, "two", 3.0, True]
print(marks)
print(len(marks))
print(subjects)
print(len(subjects))
Square brackets create a list, items are separated by commas, and len() reports how many items it contains. A list can hold values of different types, though in practice most useful lists hold one kind of thing.
Indexing
Each item has a position, called its index. Indexing starts at 0, not 1.
marks = [78, 65, 91, 45, 88]
print(marks[0])
print(marks[1])
print(marks[4])
print(marks[-1])
print(marks[-2])
So a list of five items has valid indexes 0 to 4. Asking for marks[5] raises IndexError: list index out of range.
Negative indexes count from the end: -1 is the last item, -2 the second to last. This saves you writing marks[len(marks) - 1].
Zero-based indexing is not an arbitrary annoyance. An index is best read as "how far from the start", and the first item is zero steps from the start. Almost every mainstream language does the same.
Lists are mutable, meaning you can change them after creation:
marks = [78, 65, 91, 45, 88]
marks[1] = 70
print(marks)
marks.append(59)
print(marks)
marks.insert(0, 100)
print(marks)
marks.remove(45)
print(marks)
last = marks.pop()
print(last, marks)
append(x)addsxto the end.insert(i, x)putsxat indexi, shifting the rest along.remove(x)deletes the first item equal tox, and raisesValueErrorif there is none.pop()removes and returns the last item;pop(i)does the same at indexi.
Slicing
A slice takes a section of a list, written list[start:stop]. As with range(), start is included and stop is not.
letters = ["a", "b", "c", "d", "e", "f"]
print(letters[1:4])
print(letters[:3])
print(letters[3:])
print(letters[-2:])
print(letters[::2])
print(letters[::-1])
Omit start to begin at 0; omit stop to run to the end. A third number is the step, so [::2] takes every second item and [::-1] reverses.
Slicing produces a new list. The original is untouched.
Useful list operations
marks = [78, 65, 91, 45, 88]
print(sum(marks))
print(max(marks))
print(min(marks))
print(sum(marks) / len(marks))
print(sorted(marks))
print(marks)
print(91 in marks)
print(50 in marks)
marks.sort()
print(marks)
marks.reverse()
print(marks)
Note the difference between sorted(marks) and marks.sort(). sorted() returns a new sorted list and leaves the original alone; .sort() rearranges the list in place and returns None. Writing marks = marks.sort() is a common mistake — it sets marks to None.
Looping over a list works exactly as covered in the loops tutorial:
marks = [78, 65, 91, 45, 88]
passed = 0
for m in marks:
if m >= 40:
passed += 1
print(f"{passed} of {len(marks)} students passed")
print(f"Highest: {max(marks)}, Average: {sum(marks) / len(marks):.1f}")
Strings are sequences too
A string is an ordered sequence of characters, so indexing, slicing, len(), in and for all work on strings in the same way.
name = "Bengaluru"
print(len(name))
print(name[0])
print(name[-1])
print(name[0:5])
print(name[::-1])
print("gal" in name)
for letter in "abc":
print(letter)
That consistency is deliberate: learn the idea once and it applies everywhere.
String methods
Strings come with a large set of built-in operations. These are the ones worth memorising early.
| Method | What it does |
|---|---|
.upper() / .lower() |
Change case |
.strip() |
Remove leading and trailing whitespace |
.replace(a, b) |
Replace every a with b |
.split(sep) |
Break into a list |
.find(x) |
Index of x, or -1 if absent |
.startswith(x) |
True if it begins with x |
.count(x) |
How many times x appears |
raw = " Anjali Sharma "
clean = raw.strip()
print(f"[{raw}]")
print(f"[{clean}]")
print(clean.upper())
print(clean.lower())
print(clean.replace(" ", "_"))
print(clean.split(" "))
print(clean.find("Sharma"))
print(clean.count("a"))
parts = "78,65,91".split(",")
print(parts)
numbers = [int(p) for p in parts]
print(numbers, sum(numbers))
"-".join(list) does the reverse of split, gluing a list of strings together with a separator between them.
Immutability
Here is the one genuine difference between lists and strings. Strings cannot be changed after they are created.
name = "python"
# name[0] = "P" would raise TypeError
new_name = "P" + name[1:]
print(name)
print(new_name)
upper = name.upper()
print(name)
print(upper)
Every string method that appears to modify a string actually returns a new string and leaves the original alone. So name.upper() on its own achieves nothing visible — you must store the result: name = name.upper().
List methods that change the list in place (append, sort, reverse, remove) return None, so you use them as standalone statements. String methods return a new string, so you must assign the result. If a method call seems to have done nothing, check which kind you are dealing with.
Common mistakes
Off-by-one on the last index. For a list of length 5, the last valid index is 4. marks[len(marks)] always raises IndexError. Use marks[-1] or marks[len(marks) - 1].
Assigning the result of an in-place method. marks = marks.sort() sets marks to None. Either call marks.sort() alone, or use marks = sorted(marks).
Expecting a string method to modify the string. name.upper() on its own line changes nothing. Write name = name.upper().
Copying a list by assignment.
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4] - a changed too
b = a makes both names refer to the same list. For an independent copy use b = a.copy() or b = a[:].
Confusing remove with pop and del. remove(x) deletes by value, pop(i) by index. marks.remove(0) deletes the first item whose value is 0, not the first item.
Forgetting that split() returns strings. "78,65".split(",") gives ["78", "65"] — text, not numbers. Convert each item with int() before doing arithmetic.
Practice
Try the list and string exercises on the practice page.