Class 11Computer Science · Programming with PythonFull chapter

Lists

The whole chapter in one place — read it, then test yourself. Clear notes, a reference sheet, a practice quiz, and worked NCERT solutions & PYQs.

What a List Is, and How to Index It

Quick answer A list is an ordered, mutable, mixed-type collection written in square brackets, whose items are reached by index numbers that start at 0 forwards and -1 backwards.

A list is Python's way of holding many values under one name, in order. Without lists you would write m1 = 88, m2 = 92, m3 = 76 — and then be stuck the moment a fourth student joins the class. With a list, the count stops mattering.

You write a list inside square brackets, items separated by commas. Two things surprise students who have seen arrays in C++ or Java: a Python list does not require all items to be of the same type, and it has no fixed size.

Worked example 1 — creating lists

marks = [88, 92, 76, 95, 61]
names = ['Aarav', 'Diya', 'Kabir']
mixed = [101, 'Aarav', 88.5, True]
empty = []

print(marks)
print(names)
print(mixed)
print(empty, len(empty))
print(len(marks), len(mixed))
print(type(marks))

print(list('IRCTC'))
print(list(range(1, 6)))
print(list((10, 20, 30)))

Output

[88, 92, 76, 95, 61]
['Aarav', 'Diya', 'Kabir']
[101, 'Aarav', 88.5, True]
[] 0
5 4

['I', 'R', 'C', 'T', 'C']
[1, 2, 3, 4, 5]
[10, 20, 30]

Three things to take from that output. [] is a perfectly valid empty list of length 0. len(mixed) is 4 — len() counts items, never the characters inside them. And list() is a built-in type conversion: hand it a string and you get a list of characters, hand it a range and you get the numbers, hand it a tuple and you get the same items as a list. list() with nothing inside gives [].

Indexing

Every item sits at a numbered position called its index. Counting starts at 0, not 1. The reason is worth knowing: an index is really a distance from the start. The first item is 0 steps away from the beginning, the second is 1 step away. Python also allows negative indexes, counted from the right end.

Item'Aarav''Diya''Kabir''Meera''Rohan'
Positive index01234
Negative index-5-4-3-2-1

Worked example 2 — indexing forwards and backwards

students = ['Aarav', 'Diya', 'Kabir', 'Meera', 'Rohan']

print(students[0])
print(students[3])
print(students[-1])
print(students[-5])
print(len(students))
print(students[len(students) - 1])

Output

Aarav
Meera
Rohan
Aarav
5
Rohan

Now the error you must be able to name in the exam. A 5-item list has valid indexes 0 to 4, so asking for index 5 stops the program:

students = ['Aarav', 'Diya', 'Kabir', 'Meera', 'Rohan']
print(students[5])

Error output

IndexError: list index out of range

That is why students[len(students)] is always wrong and students[len(students) - 1] is always the last item. Negative indexes exist precisely so you never have to write that subtraction: students[-1] is shorter and cannot be got wrong.

Worked example 3 — lists are mutable, strings are not

marks = [88, 92, 76]
print('before:', marks)
marks[1] = 97
print('after :', marks)

marks[-1] = 80
print('after :', marks)

Output

before: [88, 92, 76]
after : [88, 97, 76]
after : [88, 97, 80]

marks[1] = 97 changes the list itself — no new list is made. Try the same instruction on a string and Python refuses:

name = 'Aarav'
name[0] = 'B'

Error output

TypeError: 'str' object does not support item assignment

A string refuses because strings are immutable. This one difference explains most of the trick questions later in the chapter, so fix it in your head now: a list can be edited in place; a string cannot.

List literal L = [item1, item2, item3] Square brackets, commas between items. [] is a valid empty list.
list() list(iterable) -> list Type conversion. list('IRCTC') gives ['I','R','C','T','C']; list() alone gives [].
len() len(L) -> int Counts top-level items. An inner list counts as ONE item.
Positive index L[0] ... L[len(L)-1] Starts at 0. L[len(L)] is always IndexError: list index out of range.
Negative index L[-1] is last, L[-len(L)] is first Reaches the end without computing len() first.
Item assignment L[i] = value Edits the list in place. On a string this raises TypeError — strings are immutable.
Remember
  • A list is an ordered, mutable collection in square brackets; items may be of different types and the size grows and shrinks freely.
  • Indexes run 0 to len(L)-1 forwards and -1 to -len(L) backwards; anything outside that raises IndexError.
  • len(L) counts top-level items only, and list() converts a string, range or tuple into a list (list() alone gives []).
  • L[i] = value works because lists are mutable; the same statement on a string raises TypeError: 'str' object does not support item assignment.

Operations on Lists and Traversal

Quick answer Lists support + for joining, * for repeating, in for membership and [start:stop:step] for slicing — all of which build new lists — while slice assignment, del and loops let you walk through and change the original.

Once you have a list, four operators do most of the everyday work: +, *, in and slicing. The important habit to build is asking, for each one, does this change my list or hand me a new one? For all four the answer is: a new list, original untouched.

Worked example 1 — concatenation, repetition, membership

a = [1, 2, 3]
b = [4, 5]

print(a + b)
print(a)
print(b * 3)
print([0] * 5)
print([1, 2] * 0)

print(3 in a)
print(6 in a)
print(6 not in a)
print('Diya' in ['Aarav', 'Diya', 'Kabir'])

nested = [[1, 2], [3, 4]]
print([1, 2] in nested)
print(1 in nested)

Output

[1, 2, 3, 4, 5]
[1, 2, 3]
[4, 5, 4, 5, 4, 5]
[0, 0, 0, 0, 0]
[]
True
False
True
True
True
False

Read line 2 carefully: after a + b, a is still [1, 2, 3]. Concatenation builds a third list and throws it away unless you store it. [0] * 5 is the standard way to set up a list of five zeros before filling it in, and repeating a list zero (or a negative number of) times gives the empty list.

The last two in results are the exam point. [1, 2] in nested is True but 1 in nested is False, because in compares against the top-level items only — and the top-level items of nested are two lists, not four numbers.

Concatenation also insists that both sides are lists. Joining a list to a string is not allowed:

a = [1, 2, 3]
print(a + 'hi')

Error output

TypeError: can only concatenate list (not "str") to list

Worked example 2 — slicing

t = [10, 20, 30, 40, 50, 60, 70]

print(t[1:4])
print(t[:3])
print(t[4:])
print(t[:])
print(t[-3:])
print(t[:-2])
print(t[::2])
print(t[1::3])
print(t[::-1])
print(t[5:2])
print(t[2:100])
print(t)

Output

[20, 30, 40]
[10, 20, 30]
[50, 60, 70]
[10, 20, 30, 40, 50, 60, 70]
[50, 60, 70]
[10, 20, 30, 40, 50]
[10, 30, 50, 70]
[20, 50]
[70, 60, 50, 40, 30, 20, 10]
[]
[30, 40, 50, 60, 70]
[10, 20, 30, 40, 50, 60, 70]
ExpressionWhat it meansResult
t[1:4]index 1 up to but NOT including 4[20, 30, 40]
t[:3]start omitted, so from the beginning[10, 20, 30]
t[4:]stop omitted, so till the end[50, 60, 70]
t[-3:]last three items[50, 60, 70]
t[::2]every second item[10, 30, 50, 70]
t[::-1]the whole list, backwards[70, 60, 50, 40, 30, 20, 10]
t[5:2]start is after stop[] — empty, not an error
t[2:100]stop is past the end[30, 40, 50, 60, 70] — clipped, not an error

Remember the contrast: t[100] raises IndexError, but t[2:100] quietly gives you whatever exists. Slicing forgives out-of-range numbers, plain indexing does not.

Worked example 3 — slice assignment and del (these DO change the list)

q = [10, 20, 30, 40, 50]

q[1:3] = [99]
print(q)

q[1:2] = [21, 22, 23]
print(q)

del q[0]
print(q)

del q[1:3]
print(q)

Output

[10, 99, 40, 50]
[10, 21, 22, 23, 40, 50]
[21, 22, 23, 40, 50]
[21, 40, 50]

Two items were replaced by one, then one by three. The list simply resizes — an array in C++ could never do that.

Worked example 4 — traversing a list

There are two loop shapes you must be able to write. Use for item in L when you only need the values. Use for i in range(len(L)) when you need the position — for printing the index, or for changing items, since the loop variable in the first shape is only a copy of the value.

fees = [12000, 15500, 9800, 20000]

for f in fees:
    print(f, end=' ')
print()

for i in range(len(fees)):
    print('Index', i, '-> Rs', fees[i])

i = 0
while i < len(fees):
    print(fees[i] * 2, end=' ')
    i += 1
print()

total = 0
for f in fees:
    total += f
print('Total collected: Rs', total)

for i in range(len(fees)):
    fees[i] = fees[i] + 500
print(fees)

Output

12000 15500 9800 20000 
Index 0 -> Rs 12000
Index 1 -> Rs 15500
Index 2 -> Rs 9800
Index 3 -> Rs 20000
24000 31000 19600 40000 
Total collected: Rs 57300
[12500, 16000, 10300, 20500]

The last loop actually edits the list, because fees[i] = ... writes back into the list. Had you written for f in fees: f = f + 500, nothing would change — f is just a name holding a copy of the number.

The trap: never delete from a list you are looping over

L = [2, 4, 6, 8]
for x in L:
    if x % 2 == 0:
        L.remove(x)
print('after the loop:', L)

M = [2, 4, 6, 8]
keep = []
for x in M:
    if x % 2 != 0:
        keep.append(x)
print('safe version   :', keep)

Output

after the loop: [4, 8]
safe version   : []

Every number was even, so the first loop should have emptied the list — it left two behind. The loop tracks positions internally; each removal shifts everything left and the loop skips the item that slid into the vacated slot. The fix is to build a new list of what you want to keep — here nothing was odd, so keep is correctly empty.

Concatenation L1 + L2 -> new list Both sides must be lists. list + str raises TypeError: can only concatenate list to list.
Repetition L * n -> new list n must be an int. [0]*5 is the standard way to pre-fill a list; n <= 0 gives [].
Membership x in L / x not in L -> bool Compares top-level items only. Returns True/False, never the position.
Slicing L[start:stop:step] -> new list stop is excluded. Omitted start = 0, omitted stop = end. Out-of-range values are clipped, never an error.
Reverse copy L[::-1] Gives a new reversed list; L itself is untouched (unlike L.reverse()).
Slice assignment / del L[a:b] = [...] ; del L[i] ; del L[a:b] Both change L in place; the list resizes to fit the replacement.
Remember
  • +, *, in and slicing all produce a NEW list; the original is unchanged, so you must assign the result to keep it.
  • in tests top-level items only — 1 in [[1,2],[3,4]] is False while [1,2] in [[1,2],[3,4]] is True.
  • Slicing never raises IndexError: out-of-range slice numbers are clipped, and a backwards slice like t[5:2] gives [].
  • Slice assignment (L[a:b] = [...]) and del DO modify the list in place, and the replacement may be a different length.
  • Use for item in L to read values and for i in range(len(L)) when you need positions or want to change items; never remove items from the list you are iterating.

Built-in Methods: append, extend, sort and the None Trap

Quick answer The list methods split cleanly into ones that change the list and return None (append, extend, insert, remove, reverse, sort, clear) and ones that only report or copy (pop returns the removed item; count, index, copy and sorted report or copy) — and confusing the two is the single most common list mistake in the exam.

Methods are written with a dot: L.append(4). Almost every list method changes the list in place and returns None. Only count(), index() and pop() report a useful value back, and only sorted() and L.copy() hand you a new list. Keep this table in front of you while revising.

CallChanges the list?Returns
L.append(x)YesNone
L.extend(it)YesNone
L.insert(i, x)YesNone
L.remove(x)YesNone
L.pop(i)Yesthe removed item
L.clear()YesNone
L.reverse()YesNone
L.sort()YesNone
L.count(x)Noan int
L.index(x)Noan int
L.copy()Noa new list
sorted(L)Noa new list

Worked example 1 — append() vs extend(), exam trap number one

a = [1, 2, 3]
a.append(4)
print('append(4)     :', a, 'len =', len(a))

a.append([5, 6])
print('append([5,6]) :', a, 'len =', len(a))

b = [1, 2, 3]
b.extend([5, 6])
print('extend([5,6]) :', b, 'len =', len(b))

c = [1, 2]
c.extend('hi')
print("extend('hi')  :", c)

d = [1, 2]
d.append('hi')
print("append('hi')  :", d)

print('append returns:', [1, 2].append(3))
print('extend returns:', [1, 2].extend([3]))

Output

append(4)     : [1, 2, 3, 4] len = 4
append([5,6]) : [1, 2, 3, 4, [5, 6]] len = 5
extend([5,6]) : [1, 2, 3, 5, 6] len = 5
extend('hi')  : [1, 2, 'h', 'i']
append('hi')  : [1, 2, 'hi']
append returns: None
extend returns: None

Say it as a rule: append() adds exactly one item, whatever it is. extend() unpacks and adds each item separately. So append([5, 6]) makes the length go from 4 to 5 and buries a list inside a list, while extend([5, 6]) takes it from 3 to 5. Because extend() unpacks, it needs something it can walk through — a string works (characters go in one by one), a bare integer does not:

e = [1, 2]
e.extend(5)

Error output

TypeError: 'int' object is not iterable

Worked example 2 — insert, pop, remove, clear

q = ['Aarav', 'Diya', 'Kabir']
q.insert(1, 'Meera')
print(q)
q.insert(0, 'Rohan')
print(q)
q.insert(100, 'Zoya')
print(q)

x = q.pop()
print('popped', x, '->', q)
y = q.pop(0)
print('popped', y, '->', q)

n = [10, 20, 30, 20, 40]
n.remove(20)
print('after remove(20):', n)
print('remove returns  :', n.remove(20))
print('list now        :', n)

n.clear()
print('after clear():', n, len(n))

Output

['Aarav', 'Meera', 'Diya', 'Kabir']
['Rohan', 'Aarav', 'Meera', 'Diya', 'Kabir']
['Rohan', 'Aarav', 'Meera', 'Diya', 'Kabir', 'Zoya']
popped Zoya -> ['Rohan', 'Aarav', 'Meera', 'Diya', 'Kabir']
popped Rohan -> ['Aarav', 'Meera', 'Diya', 'Kabir']
after remove(20): [10, 30, 20, 40]
remove returns  : None
list now        : [10, 30, 40]
after clear(): [] 0

Four things to lock in. insert(i, x) puts x at index i and shifts everything from there to the right; an index past the end simply appends instead of erroring. pop() with no argument removes and returns the last item — this is the only removal method whose return value is worth catching. remove(x) deletes the first match by value only, which is why the first remove(20) left the second 20 alive. And clear() empties the list without destroying it.

Two removal errors are worth memorising. Removing a value that is not present:

n = [10, 30, 40]
n.remove(99)

Error output

ValueError: list.remove(x): x not in list

and popping from a list that is already empty:

n = []
n.pop()

Error output

IndexError: pop from empty list

Worked example 3 — count(), index(), and the aggregate functions

votes = ['UPI', 'Cash', 'UPI', 'Card', 'UPI', 'Cash']

print(votes.count('UPI'))
print(votes.count('NetBanking'))
print(votes.index('Cash'))
print(votes.index('Cash', 2))

nums = [5, 3, 5, 7, 5]
print(nums.count(5), nums.index(5), nums.index(5, 1))
print(len(votes), max(nums), min(nums), sum(nums))

Output

3
0
1
5
3 0 2
6 7 3 25

Note the asymmetry, which is a favourite one-mark question: a missing value makes count() return 0, but the same missing value makes index() stop the program:

votes = ['UPI', 'Cash', 'UPI', 'Card', 'UPI', 'Cash']
print(votes.index('NetBanking'))

Error output

ValueError: 'NetBanking' is not in list

Give index() a second argument and it starts hunting from that position, which is how you find the second occurrence.

Worked example 4 — sort() vs sorted(), exam trap number two

m = [88, 92, 76, 95, 61]

m.reverse()
print('after reverse():', m)
print('reverse returns:', [1, 2, 3].reverse())

m.sort()
print('after sort()   :', m)
print('sort returns   :', [3, 1, 2].sort())

orig = [88, 92, 76, 95, 61]
new = sorted(orig)
print('sorted() gives :', new)
print('original intact:', orig)

wrong = orig.sort()
print('wrong =', wrong)

Output

after reverse(): [61, 95, 76, 92, 88]
reverse returns: None
after sort()   : [61, 76, 88, 92, 95]
sort returns   : None
sorted() gives : [61, 76, 88, 92, 95]
original intact: [88, 92, 76, 95, 61]
wrong = None

This is the mistake that costs marks every single year. L.sort() rearranges L and returns None, so wrong = orig.sort() stores None. Everything looks fine until the next line tries to use it:

orig = [88, 92, 76, 95, 61]
wrong = orig.sort()
print(wrong[0])

Error output

TypeError: 'NoneType' object is not subscriptable

Write L.sort() on a line by itself, or use sorted(L) when you want a sorted copy and need the original order preserved. Note also that reverse() is not a sort: it flips the list into its opposite order exactly as it stands, which is why [88, 92, 76, 95, 61] became [61, 95, 76, 92, 88] and not a descending list.

Worked example 5 — sorting options and what cannot be sorted

m = [88, 92, 76, 95, 61]
m.sort(reverse=True)
print(m)
print(sorted(m))
print(sorted(m, reverse=True))

names = ['diya', 'Aarav', 'kabir', 'Bhavna']
print(sorted(names))

print(sorted('IRCTC'))
print(sorted((30, 10, 20)))

Output

[95, 92, 88, 76, 61]
[61, 76, 88, 92, 95]
[95, 92, 88, 76, 61]
['Aarav', 'Bhavna', 'diya', 'kabir']
['C', 'C', 'I', 'R', 'T']
[10, 20, 30]

Capital letters sort before small letters because sorting compares ASCII codes, and every capital (65-90) is smaller than every small letter (97-122). sorted() accepts any iterable and always returns a list, which is why sorted('IRCTC') gives a list of characters and sorted((30, 10, 20)) turns a tuple into a list. Mixing numbers and strings fails, because Python has no rule for whether 3 comes before 'one':

print(sorted([3, 'one', 2]))

Error output

TypeError: '<' not supported between instances of 'str' and 'int'
append() L.append(x) -> None Adds x as ONE element at the end. len(L) always grows by exactly 1.
extend() L.extend(iterable) -> None Adds each item of the iterable separately. L.extend(5) -> TypeError: 'int' object is not iterable.
insert() L.insert(i, x) -> None Places x AT index i and shifts the rest right. An index beyond the end just appends.
pop() / remove() / clear() L.pop([i]) -> item ; L.remove(x) -> None ; L.clear() -> None pop defaults to the last item and returns it; remove deletes the first match by value (ValueError if absent); pop on [] raises IndexError.
count() / index() L.count(x) -> int ; L.index(x[, start]) -> int count gives 0 when absent; index raises ValueError when absent and reports only the FIRST position.
sort() vs sorted() L.sort([reverse=True]) -> None ; sorted(L[, reverse=True]) -> new list sort()/reverse() work in place and return None; sorted() copies. Mixed int and str raises TypeError.
Remember
  • append(x) adds exactly one item (length +1 always); extend(iterable) unpacks and adds each item, so extend(5) is a TypeError but extend('hi') adds 'h' and 'i'.
  • sort(), reverse(), append(), extend(), insert(), remove() and clear() all return None — writing x = L.sort() stores None and the next indexing gives TypeError: 'NoneType' object is not subscriptable.
  • sorted(L) returns a new sorted list and leaves L untouched; L.sort() rearranges L itself.
  • pop() returns the removed item (last by default); remove(x) deletes only the FIRST match by value and returns None.
  • A missing value makes count() return 0 but makes index() raise ValueError; pop() on an empty list raises IndexError.

Mutability, Aliasing and Copying

Quick answer Because b = a copies only the label and not the list, changing a is visible through b — proved with id() — and the real copies are a[:], list(a) and a.copy().

Here is the idea that genuinely surprises people. A list variable does not hold the list. It holds a label pointing to the list, which lives somewhere in memory. The built-in id() shows you the address of that object. When you write b = a, Python copies the label, not the list — so now two names point at the same object. That is called aliasing.

Worked example 1 — b = a does not make a copy

a = [10, 20, 30]
b = a

print('id(a) == id(b) ?', id(a) == id(b))
print('same object?', a is b)

a.append(40)
print('a =', a)
print('b =', b)

b[0] = 99
print('a =', a)
print('b =', b)

a = a + [50]
print('after a = a + [50]')
print('a =', a)
print('b =', b)
print('same object?', a is b)

Output

id(a) == id(b) ? True
same object? True
a = [10, 20, 30, 40]
b = [10, 20, 30, 40]
a = [99, 20, 30, 40]
b = [99, 20, 30, 40]
after a = a + [50]
a = [99, 20, 30, 40, 50]
b = [99, 20, 30, 40]
same object? False

Printing id(a) on its own would show a long number such as 2933657855680, and that number is different on every machine and on every run — which is why the program above compares the two ids instead of printing them. What matters is only that they are equal. Appending to a changed what b shows, and assigning through b changed what a shows, because there is only one list.

The last part is the finer point. a = a + [50] builds a brand-new list and points a at it — that is rebinding, not mutating. b is left holding the old object, the ids stop matching, and the two names go their separate ways from then on. Compare: a.append(50) would have shown up in b. Same-looking operation, completely different effect.

Worked example 2 — three ways to make a real copy

a = [10, 20, 30]
b = a[:]
c = list(a)
d = a.copy()

print(a is b, a is c, a is d)
print(a == b, a == c, a == d)

a.append(40)
print('a =', a)
print('b =', b)
print('c =', c)
print('d =', d)

x = [[1, 2], [3, 4]]
y = x[:]
print('x is y ?', x is y)
y[0][0] = 99
print('x =', x)
print('y =', y)

Output

False False False
True True True
a = [10, 20, 30, 40]
b = [10, 20, 30]
c = [10, 20, 30]
d = [10, 20, 30]
x is y ? False
x = [[99, 2], [3, 4]]
y = [[99, 2], [3, 4]]

The first two lines settle the difference between is and ==. a == b asks "same values?" and is True. a is b asks "same object in memory?" and is False. Appending to a now leaves all three copies alone, which is exactly what you wanted.

The last block is the honest footnote: x[:] copies the outer list but the two inner lists are still shared, so editing y[0][0] is visible in x. This is called a shallow copy. You will not be asked to fix it in Class 11, but you should recognise it if you see it.

Worked example 3 — the nested repetition trap

grid = [[0] * 3] * 3
print(grid)
grid[0][0] = 1
print(grid)
print(grid[0] is grid[1])

grid2 = [[0] * 3, [0] * 3, [0] * 3]
grid2[0][0] = 1
print(grid2)
print(grid2[0] is grid2[1])

Output

[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 0, 0], [1, 0, 0], [1, 0, 0]]
True
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
False

[[0] * 3] * 3 looks like a clean 3x3 grid and prints like one. But * repeats the reference, so all three rows are the same single row object — grid[0] is grid[1] is True — and setting one cell sets it in all three rows. Writing the three rows out separately, or building them one at a time with append(), gives three independent rows.

Worked example 4 — why a "backup" made with = is not a backup

This is where aliasing actually bites in a program. You keep a copy of a list before sorting it, then discover the copy got sorted too.

original = [30, 10, 20]
backup = original
original.sort()
print('original :', original)
print('backup   :', backup)
print('is backup a real copy?', original is not backup)

original2 = [30, 10, 20]
backup2 = original2[:]
original2.sort()
print('original2:', original2)
print('backup2  :', backup2)
print('is backup2 a real copy?', original2 is not backup2)

safe = sorted(original2, reverse=True)
print('sorted copy:', safe)
print('untouched  :', original2)

Output

original : [10, 20, 30]
backup   : [10, 20, 30]
is backup a real copy? False
original2: [10, 20, 30]
backup2  : [30, 10, 20]
is backup2 a real copy? True
sorted copy: [30, 20, 10]
untouched  : [10, 20, 30]

The first backup was never a backup — it was a second label on the very list that sort() rearranged. backup2 = original2[:] makes a genuine copy, so the original order survives. And the third block shows the neatest solution of all: sorted() gives you the sorted version and leaves the list alone, so you never need a backup in the first place.

id() id(obj) -> int Identity of the object in memory. Equal ids mean the SAME object, not a copy. The number changes every run.
is vs == a is b (same object) ; a == b (same values) Two separate lists with equal contents give True for == and False for is.
Aliasing b = a No copy is made. Any mutation of a shows up in b and vice versa.
Copying a list b = a[:] | b = list(a) | b = a.copy() All three make a genuinely new outer list. Inner lists are still shared (shallow copy).
Mutate vs rebind a.append(x) vs a = a + [x] append changes the shared object; a + [x] builds a new list and leaves the alias on the old one.
Nested repetition trap [[0]*3]*3 Three references to ONE row. Use [[0]*3, [0]*3, [0]*3] or append rows in a loop.
Remember
  • b = a copies the label, not the list — id(a) == id(b) and a is b is True, so mutating either name is visible through both.
  • a.append(x) mutates the shared list, but a = a + [x] rebinds a to a brand-new list and quietly breaks the link with b.
  • Real copies: a[:], list(a) or a.copy(); after any of them a is b is False while a == b is True.
  • [[0]*3]*3 repeats one row reference three times, so grid[0][0] = 1 changes every row — build nested lists row by row instead.
  • backup = original is not a backup: original.sort() sorts backup too. Use backup = original[:], or use sorted(original) and leave the list alone.

Nested Lists and the Standard Programs

Quick answer A list whose items are themselves lists gives you a table addressed as L[i][j], and on top of it the three syllabus programs — maximum/minimum/mean, linear search and frequency count — are each a single traversal loop.

A list item can itself be a list. That gives you a table: the outer list holds rows, each row holds the cells. This is exactly how a marksheet is stored before it is printed.

Worked example 1 — a nested list as a marksheet

report = [['Aarav', 88, 92, 76],
          ['Diya', 95, 89, 91],
          ['Kabir', 61, 70, 68]]

print(len(report))
print(len(report[0]))
print(report[0])
print(report[0][0])
print(report[1][2])
print(report[-1][-1])

for row in report:
    name = row[0]
    marks = row[1:]
    print(name, marks, 'total =', sum(marks),
          'avg =', round(sum(marks) / len(marks), 2))

report[2][1] = 71
print(report[2])

report.append(['Meera', 80, 85, 90])
print(len(report))
print(report[3])

Output

3
4
['Aarav', 88, 92, 76]
Aarav
89
68
Aarav [88, 92, 76] total = 256 avg = 85.33
Diya [95, 89, 91] total = 275 avg = 91.67
Kabir [61, 70, 68] total = 199 avg = 66.33
['Kabir', 71, 70, 68]
4
['Meera', 80, 85, 90]

Read report[1][2] left to right: report[1] picks the row ['Diya', 95, 89, 91], then [2] picks item 2 of that row, which is 89. len(report) counts rows and len(report[0]) counts the cells in a row. Slicing works inside a row too — row[1:] drops the name and leaves only the marks, ready for sum().

The aggregate built-in functions

nums = [88, 92, 76, 95, 61]
print(max(nums), min(nums), sum(nums), len(nums))

names = ['Aarav', 'Diya', 'Kabir', 'Bhavna']
print(max(names), min(names))

print(sum([]), len([]))

Output

95 61 412 5
Kabir Aarav
0 0

max() and min() work on strings too, using dictionary order — 'Kabir' is the largest name here. But sum() insists on numbers:

names = ['Aarav', 'Diya', 'Kabir', 'Bhavna']
print(sum(names))

Error output

TypeError: unsupported operand type(s) for +: 'int' and 'str'

The empty list is the edge case to remember: sum([]) is a peaceful 0, but max([]) and min([]) have nothing to return and raise ValueError:

print(max([]))

Error output

ValueError: max() iterable argument is empty

(The exact wording of that message changed between Python versions — older releases say max() arg is an empty sequence — but it is a ValueError either way.)

Program 1 — maximum, minimum and mean of a list of numbers

The exam usually wants the loop version, so learn both and cross-check one against the other.

marks = [88, 92, 76, 95, 61, 84]

print('--- using built-in functions ---')
print('Maximum:', max(marks))
print('Minimum:', min(marks))
print('Sum    :', sum(marks))
print('Mean   :', sum(marks) / len(marks))
print('Mean   :', round(sum(marks) / len(marks), 2))

print('--- using a loop (exam version) ---')
hi = marks[0]
lo = marks[0]
total = 0
for m in marks:
    if m > hi:
        hi = m
    if m < lo:
        lo = m
    total += m
print('Maximum:', hi)
print('Minimum:', lo)
print('Mean   :', round(total / len(marks), 2))

Output

--- using built-in functions ---
Maximum: 95
Minimum: 61
Sum    : 496
Mean   : 82.66666666666667
Mean   : 82.67
--- using a loop (exam version) ---
Maximum: 95
Minimum: 61
Mean   : 82.67

Start hi and lo at marks[0], never at 0. If you start hi at 0 the program breaks for all-negative data, and if you start lo at 0 it will report 0 as the minimum of a list that has no zero in it. The mean uses /, so it is always a float — round(x, 2) makes it printable.

The same thing with the statistics module

import statistics

marks = [88, 92, 76, 95, 61]
print('mean  :', statistics.mean(marks))
print('median:', statistics.median(marks))
print('mean of a copy still works:', statistics.mean(sorted(marks)))

Output

mean  : 82.4
median: 88
mean of a copy still works: 82.4

Program 2 — linear search on a list of numbers

Linear search means: walk from the start, compare each item with the key, stop the moment it matches. It is the only search you can use when the list is not sorted.

rolls = [1102, 1107, 1115, 1121, 1130]

key = 1115
pos = -1
for i in range(len(rolls)):
    if rolls[i] == key:
        pos = i
        break
if pos == -1:
    print(key, 'not found')
else:
    print(key, 'found at index', pos, '- that is position', pos + 1)

key = 1200
pos = -1
for i in range(len(rolls)):
    if rolls[i] == key:
        pos = i
        break
if pos == -1:
    print(key, 'not found')
else:
    print(key, 'found at index', pos)

print(1115 in rolls)
print(rolls.index(1115))

Output

1115 found at index 2 - that is position 3
1200 not found
True
2

The pattern is fixed and worth memorising: set pos = -1 before the loop, overwrite it on a match, break out, then test pos == -1 afterwards. -1 is used as the "not found" marker because it can never be a real result of range(len(L)). Python's own in and index() do the same job, but an exam question that says "using linear search" wants the loop written out.

Program 3 — counting the frequency of elements

votes = ['UPI', 'Cash', 'UPI', 'Card', 'UPI', 'Cash', 'Card', 'UPI']

done = []
for v in votes:
    if v not in done:
        print(v, '->', votes.count(v))
        done.append(v)

nums = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
seen = []
freq = []
for n in nums:
    if n not in seen:
        seen.append(n)
        freq.append(nums.count(n))
print(seen)
print(freq)

maxpos = 0
for i in range(len(freq)):
    if freq[i] > freq[maxpos]:
        maxpos = i
print('Most frequent value:', seen[maxpos], 'occurring', freq[maxpos], 'times')

Output

UPI -> 4
Cash -> 2
Card -> 2
[3, 1, 4, 5, 9, 2, 6]
[2, 2, 1, 3, 1, 1, 1]
Most frequent value: 5 occurring 3 times

The done list is what stops UPI from being reported four times. count() gives the number, and the not in check gives each distinct value exactly one turn. The second half stores the distinct values and their counts in two parallel lists, so seen[i] and freq[i] always describe the same value — then finding the most frequent element is the same maximum loop as before, run on freq but remembering the position rather than the value.

Nested indexing L[i][j] L[i] picks the inner list, then [j] picks inside it. Slicing works too: row[1:] drops the first cell.
max() / min() max(L) -> item ; min(L) -> item Works on numbers and on strings (dictionary order, capitals first). Empty list raises ValueError.
sum() sum(L) -> number ; sum(L, start) Numbers only — summing strings raises TypeError. sum([]) is 0.
Mean of a list sum(L) / len(L) Always a float. Use round(value, 2) to print. Guard against len(L) == 0.
statistics module import statistics ; statistics.mean(L) ; statistics.median(L) Only math, random and statistics may be imported in the Class 11 syllabus.
Linear search pos = -1 ; for i in range(len(L)): if L[i] == key: pos = i; break After the loop, pos == -1 means not found; otherwise pos is the index and pos+1 the position.
Remember
  • A nested list is a table: L[i] is a row, L[i][j] is a cell, len(L) counts rows and len(L[0]) counts columns.
  • max(), min(), sum() and len() are built-in FUNCTIONS taking the list as an argument, not dot-methods; sum([]) is 0 but max([]) raises ValueError.
  • Mean = sum(L) / len(L) and is always a float; when writing the loop version, start hi and lo at L[0], never at 0.
  • Linear search pattern: pos = -1 before the loop, set pos and break on a match, then test pos == -1 after the loop.
  • Frequency count without repeats: keep a 'seen' list, and for each value not already in it use L.count(value).

The formula sheet

Every formula in this chapter, in one place — screenshot it before your exam.

L = [item1, item2, item3]
List literal
list(iterable) -> list
list()
len(L) -> int
len()
L[0] ... L[len(L)-1]
Positive index
L[-1] is last, L[-len(L)] is first
Negative index
L[i] = value
Item assignment
L1 + L2 -> new list
Concatenation
L * n -> new list
Repetition
x in L / x not in L -> bool
Membership
L[start:stop:step] -> new list
Slicing
L[::-1]
Reverse copy
L[a:b] = [...] ; del L[i] ; del L[a:b]
Slice assignment / del
L.append(x) -> None
append()
L.extend(iterable) -> None
extend()
L.insert(i, x) -> None
insert()
L.pop([i]) -> item ; L.remove(x) -> None ; L.clear() -> None
pop() / remove() / clear()
L.count(x) -> int ; L.index(x[, start]) -> int
count() / index()
L.sort([reverse=True]) -> None ; sorted(L[, reverse=True]) -> new list
sort() vs sorted()
id(obj) -> int
id()
a is b (same object) ; a == b (same values)
is vs ==
b = a
Aliasing
b = a[:] | b = list(a) | b = a.copy()
Copying a list
a.append(x) vs a = a + [x]
Mutate vs rebind
[[0]*3]*3
Nested repetition trap
L[i][j]
Nested indexing
max(L) -> item ; min(L) -> item
max() / min()
sum(L) -> number ; sum(L, start)
sum()
sum(L) / len(L)
Mean of a list
import statistics ; statistics.mean(L) ; statistics.median(L)
statistics module
pos = -1 ; for i in range(len(L)): if L[i] == key: pos = i; break
Linear search

Test yourself

Tap an answer to check it instantly — you'll see why it's right, and what to revise if it isn't.

0 correct · 0/12 answered
Q1

What is the output of:L = [1, 2, 3]L2 = L.append(4)print(L2, L)

Q2

What is the output of:L = [10, 20, 30, 40, 50, 60]print(L[-4:-1])

Q3

What is the output of:L = [5, 2, 9]print(sorted(L), L)

Q4

What is the output of:a = [1, 2, 3]b = ab.append(4)print(a, len(a))

Q5

What is the output of:L = [1, 2, 3]L.append([4, 5])M = [1, 2, 3]M.extend([4, 5])print(len(L), len(M))

Q6

What is the output of:L = [1, 2, 3, 2, 1, 2]print(L.index(2), L.count(2))

Q7

What is the output of:L = ['a', 'b', 'c']L.insert(5, 'd')print(L)

Q8

What is the output of:L = [1, 2, 3, 4, 5]del L[1:3]print(L)

Q9

Which one of these statements raises an error?

Q10

For L = [10, 20, 30, 20], what does print(L.remove(20)) display, and what is L afterwards?

Q11

What is the output of:print(1 in [[1, 2], [3, 4]], [1, 2] in [[1, 2], [3, 4]])

Q12

What is the output of:grid = [[0] * 3] * 3grid[0][0] = 1print(grid)

NCERT solutions & previous-year questions

Step-by-step model answers — tap a question to reveal the full solution.

NCERT questions 6

1 Consider the list myList = [10, 20, 30, 40]. What will myList contain after myList.append([50, 60]) is executed, and then after myList.extend([80, 90]) is executed? State the length of the list at each stage.append() vs extend()

Reasoning. append() puts its argument in as a single element, no matter what that argument is. So append([50, 60]) stores the whole list [50, 60] as one item and the length grows by exactly 1. extend() walks through its argument and adds each item separately, so extend([80, 90]) adds two items and the length grows by 2.

myList = [10, 20, 30, 40]
myList.append([50, 60])
print(myList)
print(len(myList))
print(myList[4])

myList.extend([80, 90])
print(myList)
print(len(myList))

Output

[10, 20, 30, 40, [50, 60]]
5
[50, 60]
[10, 20, 30, 40, [50, 60], 80, 90]
7

Answer. After the append, myList is [10, 20, 30, 40, [50, 60]] with length 5 — it has become a nested list, and myList[4] is itself a list. After the extend, myList is [10, 20, 30, 40, [50, 60], 80, 90] with length 7. The nested [50, 60] stays nested; extend() does not flatten what is already inside.

2 Predict the output of each part below. In every part, list1 is freshly created as list1 = [12, 32, 65, 26, 80, 10] before the statements are run.(i) list1.sort(); print(list1)(ii) sorted(list1); print(list1)(iii) list1.reverse(); print(list1)(iv) print(list1.pop()); print(list1)(v) print(list1.remove(32)); print(list1)sort(), sorted(), reverse(), pop(), remove()
list1 = [12, 32, 65, 26, 80, 10]
list1.sort()
print(list1)

list1 = [12, 32, 65, 26, 80, 10]
sorted(list1)
print(list1)

list1 = [12, 32, 65, 26, 80, 10]
list1.reverse()
print(list1)

list1 = [12, 32, 65, 26, 80, 10]
print(list1.pop())
print(list1)

list1 = [12, 32, 65, 26, 80, 10]
print(list1.remove(32))
print(list1)

Output

[10, 12, 26, 32, 65, 80]
[12, 32, 65, 26, 80, 10]
[10, 80, 26, 65, 32, 12]
10
[12, 32, 65, 26, 80]
None
[12, 65, 26, 80, 10]

Explanations.

  • (i) sort() rearranges list1 itself into ascending order.
  • (ii) sorted(list1) builds a sorted copy — but the copy was never stored in a variable, so it is thrown away and list1 prints in its original order. This is the part most students get wrong.
  • (iii) reverse() flips the existing order in place. It does not sort: the result is the original list read backwards, not descending order.
  • (iv) pop() with no argument removes the last item and returns it, so 10 is printed and the list is left with five items.
  • (v) remove(32) deletes the value 32 but returns None, so print() shows None. The list itself loses the 32.
3 Write a program to find the largest and the smallest element of a list without using the built-in max() and min() functions.Traversal, maximum and minimum

Method. Assume the first element is both the largest and the smallest, then walk through the list once. Each time you meet something bigger than the current largest, that becomes the new largest; the same idea in reverse for the smallest.

nums = [45, 12, 89, 33, 7, 64]

largest = nums[0]
smallest = nums[0]
for x in nums:
    if x > largest:
        largest = x
    if x < smallest:
        smallest = x

print('List    :', nums)
print('Largest :', largest)
print('Smallest:', smallest)
print('Check with built-ins:', max(nums), min(nums))

Output

List    : [45, 12, 89, 33, 7, 64]
Largest : 89
Smallest: 7
Check with built-ins: 89 7

The mark-losing mistake. Do not start with largest = 0 and smallest = 0. If every number is negative, largest stays 0 and the answer is a value that is not even in the list; and smallest = 0 would report 0 as the minimum of [45, 12, 89]. Always seed both from nums[0], which is guaranteed to be a real member of the list.

4 Write a program that reads n numbers from the user, stores them in a list, and prints their sum and average.Building a list from input, sum and mean

Method. Start with an empty list, read n values in a loop and append() each one. Then sum(nums) gives the total and sum(nums) / len(nums) gives the average. Remember input() always returns a string, so each value must be passed through int().

n = int(input('How many numbers? '))
nums = []
for i in range(n):
    v = int(input('Enter number ' + str(i + 1) + ': '))
    nums.append(v)

print()
print('List    :', nums)
print('Sum     :', sum(nums))
print('Average :', sum(nums) / n)
print('Average :', round(sum(nums) / n, 2))

Sample run (the value after each prompt is what the user types)

How many numbers? 5
Enter number 1: 12
Enter number 2: 45
Enter number 3: 7
Enter number 4: 30
Enter number 5: 6

List    : [12, 45, 7, 30, 6]
Sum     : 100
Average : 20.0
Average : 20.0

Note. The average prints as 20.0, not 20, because / always produces a float even when the division is exact. That is correct and expected — do not "fix" it with //, which would give a wrong average for data like [1, 2]. Also note sum(nums) / n and sum(nums) / len(nums) are the same thing here, and len(nums) is the safer habit because it stays right even if the loop stored fewer values than expected.

5 Write a program to count the number of times a given element occurs in a list, without using the count() method.Frequency counting by traversal

Method. Set a counter to 0 before the loop, walk the whole list, and add 1 every time the item equals the key. The counter must be set up outside the loop, otherwise it resets on every pass and you always end with 0 or 1.

nums = [4, 7, 4, 9, 4, 2, 7]
key = 4

count = 0
for x in nums:
    if x == key:
        count += 1
print('List :', nums)
print(key, 'occurs', count, 'time(s)')
print('Cross-check with count():', nums.count(key))

key = 100
count = 0
for x in nums:
    if x == key:
        count += 1
print(key, 'occurs', count, 'time(s)')

Output

List : [4, 7, 4, 9, 4, 2, 7]
4 occurs 3 time(s)
Cross-check with count(): 3
100 occurs 0 time(s)

Note. When the key is absent the loop simply never runs its if body and the answer is 0 — no error. That matches count(), which also returns 0 for a missing value. Contrast this with index(), which would raise ValueError for a missing value instead.

6 Write a program to swap the first and the last element of a list.Indexing and item assignment

Method. Because lists are mutable, you can write straight into positions 0 and -1. The classic way uses a temporary variable so that the first value is not lost when it is overwritten. Python also allows a one-line swap using tuple assignment, where the right-hand side is fully evaluated before anything is stored.

nums = [11, 22, 33, 44, 55]
print('Before:', nums)

temp = nums[0]
nums[0] = nums[-1]
nums[-1] = temp

print('After :', nums)

other = [11, 22, 33, 44, 55]
other[0], other[-1] = other[-1], other[0]
print('One-line swap:', other)

single = [7]
single[0], single[-1] = single[-1], single[0]
print('Single element:', single)

Output

Before: [11, 22, 33, 44, 55]
After : [55, 22, 33, 44, 11]
One-line swap: [55, 22, 33, 44, 11]
Single element: [7]

Why the temp variable is needed in the long version. If you wrote nums[0] = nums[-1] followed by nums[-1] = nums[0], the first line would already have destroyed the original 11, and the second line would copy 55 back onto itself — you would end up with 55 at both ends. Also note that using nums[-1] instead of nums[4] makes the program work for a list of any length, and a one-element list is handled harmlessly since index 0 and index -1 are the same slot.

Previous-year board questions 4

Q1 Predict the output of the following code and justify each line:L = [1, 2, 3, 4, 5]L.insert(2, 10)print(L)L.append([6, 7])print(L)print(len(L))L.pop(0)print(L)print(L[-1])print(L[-1][0]) 2025-26 pattern, 2 marks
L = [1, 2, 3, 4, 5]
L.insert(2, 10)
print(L)
L.append([6, 7])
print(L)
print(len(L))
L.pop(0)
print(L)
print(L[-1])
print(L[-1][0])

Output

[1, 2, 10, 3, 4, 5]
[1, 2, 10, 3, 4, 5, [6, 7]]
7
[2, 10, 3, 4, 5, [6, 7]]
[6, 7]
6

Line by line.

  • insert(2, 10) places 10 at index 2; the old occupant 3 and everything after it shift one place right. Length goes 5 to 6.
  • append([6, 7]) adds the list [6, 7] as a single element, so the length becomes 7, not 8. Had it been extend([6, 7]) the length would have been 8.
  • pop(0) removes the item at index 0 (the value 1) and returns it — the returned value is discarded here because it was not printed or stored.
  • L[-1] is now the nested list [6, 7], so it prints with its brackets; L[-1][0] then reaches inside it and gives 6.
Q2 Differentiate between the sort() method and the sorted() function, giving a suitable example of each. Also explain why the statement newList = myList.sort() is a mistake. 2025-26 pattern, 2 marks
PointL.sort()sorted(L)
Typea list method (dot notation)a built-in function
Effect on the originalrearranges L itself (in place)leaves L completely unchanged
Return valueNonea new sorted list
Works onlists onlyany iterable — string, tuple, range — and always returns a list
L = [30, 10, 20]
A = sorted(L)
print('A =', A, '  L =', L)

B = L.sort()
print('B =', B, '  L =', L)

C = [5, 1, 3]
print(sorted(C, reverse=True), C)
C.sort(reverse=True)
print(C)

Output

A = [10, 20, 30]   L = [30, 10, 20]
B = None   L = [10, 20, 30]
[5, 3, 1] [5, 1, 3]
[5, 3, 1]

Why newList = myList.sort() is a mistake. sort() does its work by modifying the list and then returns None, so newList is bound to None rather than to a list. The program looks fine until the next line tries to use newList, at which point you get TypeError: 'NoneType' object is not subscriptable for indexing, or TypeError: object of type 'NoneType' has no len() for len(). The correct forms are either myList.sort() written on its own line, or newList = sorted(myList) if the original order must be preserved.

Q3 A list Sal stores the monthly salaries (in rupees) of the employees of a firm. Write a program to compute and display the average salary, the number of employees earning above the average, and those salary values. 2025-26 pattern, 3 marks

Method. Compute the average first, in one pass using sum() and len(). Then make a second pass with a counter. The average must be worked out before the comparison loop starts — you cannot compare against an average you have not finished computing.

sal = [25000, 42000, 18000, 60000, 31000, 27500]

avg = sum(sal) / len(sal)
print('Total   : Rs', sum(sal))
print('Average : Rs', round(avg, 2))

count = 0
for s in sal:
    if s > avg:
        count += 1
print('Number of salaries above average:', count)

print('They are:', end=' ')
for s in sal:
    if s > avg:
        print(s, end=' ')
print()

Output

Total   : Rs 203500
Average : Rs 33916.67
Number of salaries above average: 2
They are: 42000 60000 

Check. The six salaries total ₹2,03,500, and 203500 / 6 = 33916.666..., which rounds to ₹33,916.67 for display. Only 42000 and 60000 exceed it, so the count of 2 is right. Note that it is normal for most employees to be below average — one large salary of ₹60,000 drags the mean above the middle of the data, which is exactly why the median is sometimes a fairer measure.

Q4 A nested list stores, for each student, the name followed by the marks in three subjects. Write a program to display each student's total and percentage, identify the topper, and compute the class average of the first subject. 2025-26 pattern, 4 marks

Method. Each row is [name, m1, m2, m3], so row[0] is the name and row[1:] is a list of just the marks — ready to be handed to sum(). For the topper, use the same maximum pattern as before, but remember the name alongside the best total. Seed both from row 0 so the program is correct even if every total is unusually low.

data = [['Aarav', 88, 92, 76],
        ['Diya', 95, 89, 91],
        ['Kabir', 61, 70, 68],
        ['Meera', 90, 94, 99]]

topper = data[0][0]
best = sum(data[0][1:])

for row in data:
    t = sum(row[1:])
    print(row[0], '-> total', t, ', percent', round(t / 3, 2))
    if t > best:
        best = t
        topper = row[0]

print('Topper:', topper, 'with', best, 'out of 300')

maths = []
for row in data:
    maths.append(row[1])
print('Maths marks:', maths)
print('Maths class average:', round(sum(maths) / len(maths), 2))

Output

Aarav -> total 256 , percent 85.33
Diya -> total 275 , percent 91.67
Kabir -> total 199 , percent 66.33
Meera -> total 283 , percent 94.33
Topper: Meera with 283 out of 300
Maths marks: [88, 95, 61, 90]
Maths class average: 83.5

Points the examiner looks for. First, row[1:] rather than row — passing the whole row to sum() would try to add the name and raise TypeError: unsupported operand type(s) for +: 'int' and 'str'. Second, the percentage is the total out of 300, so it is t / 3. Third, extracting a whole column means looping over the rows and picking the same index from each — row[1] here gives [88, 95, 61, 90], averaging 83.5.

Part of Priodemy for School

Interactive CBSE lessons, Class 8–12 — free with every school on Priodemy EduSuite. Explore more chapters and labs on the Priodemy for School hub.

Ask AI