📌 Snapshot
- Why a billion sorted records need only about 30 steps: linear, binary and hash search explained with traces.
- How the three searches differ in what they need (nothing, a sorted list, or a hash table) and how many comparisons they make.
- Worked traces for linear search, binary search (found and not found) and a hash table, plus collisions and perfect hash functions.
- The Python programs for all three searches, written out line by line so you can predict their output in a test.
📖 Detailed Notes
Written and reviewed by Rohit Singh, Computer Science & Commerce faculty at UniDrill (MCA, Osmania University · 6 years teaching). Based on NCERT Computer Science Class XII, Chapter 6.
How does a phone find one contact among a billion?
Open a dictionary and you do not start at page one. You flip to the middle, see that the word you want comes later, and throw away the first half in one move. A computer can do the same with data, and the trick is so strong that a sorted list of about a billion records can be searched in roughly 30 steps instead of a billion. This chapter is about three ways of searching a list, what each one demands from your data, and how much work each one does.
Searching means locating a particular element, called the key, in a collection. The result tells you whether the key is present and, if it is, where.
| Technique | What the list must look like | Work in the worst case | Idea in one line |
|---|---|---|---|
| Linear search | Anything: sorted or unsorted | $n$ comparisons | Check every element in order |
| Binary search | Must be sorted | About $\log_2 n$ iterations | Compare with the middle and discard half |
| Search by hashing | Stored in a hash table built by a hash function | 1 comparison if there is no collision | Calculate where the key must be |
Linear search: check every element in order
Linear search (also called sequential or serial search) compares the key with each element one by one, from the first towards the last. It stops with success at the first match, or with failure after the whole list has been checked. It needs no order at all, so it is the right tool for a small, unordered list.
The algorithm sets index = 0, repeats while index < n, compares numList[index] with the key, prints index + 1 as the position when they match, and otherwise increments the index. Notice the last step: the position reported to the user is index + 1, because people count from 1 while Python counts from 0.
Worked example: tracing linear search. Let numList = [8, -4, 7, 17, 0, 2, 19], so n = 7, and search for the key 17.
| index | index < n | numList[index] == 17 ? | Next index |
|---|---|---|---|
| 0 | 0 < 7, yes | 8 = 17? No | 1 |
| 1 | 1 < 7, yes | -4 = 17? No | 2 |
| 2 | 2 < 7, yes | 7 = 17? No | 3 |
| 3 | 3 < 7, yes | 17 = 17? Yes | stop |
The key is found after 4 comparisons at index 3, so the program reports position 4. The common slip is to report 3.
How much work linear search does depends only on where the key sits. The same seven numbers show all three cases.
| Where the key is | Example list (key 17) | Comparisons | Case |
|---|---|---|---|
| First element | [17, 8, -4, 7, 0, 2, 19] | 1 | Best case |
| Inside the list | [8, -4, 7, 17, 0, 2, 19] | 4 | Between the two extremes |
| Last element | [8, -4, 7, 0, 2, 19, 17] | 7 = $n$ | Worst case |
| Not present at all | any list without 17 | 7 = $n$ | Worst case |
Failing is as expensive as finding the last element, because only after looking at everything can you say "not here". The time grows in step with the size of the list, which is why linear search becomes slow for large lists.
Program: linear search as a function. The function returns the position (index plus one) or None.
| Code |
|---|
def linearSearch(list, key): |
for index in range(0, len(list)): |
if list[index] == key: |
return index + 1 |
return None |
Calling it on the list from the trace:
| Code |
|---|
list1 = [8, -4, 7, 17, 0, 2, 19] |
position = linearSearch(list1, 17) |
if position is None: |
print("Number 17 is not present in the list") |
else: |
print("Number 17 is present at position", position) |
| Output |
|---|
Number 17 is present at position 4 |
If the list holds duplicates, such as [42, -2, 32, 8, 17, 19, 42, 13, 8, 44], searching for 8 returns position 4. The loop returns as soon as it meets the first match, so later copies are never reached.
Pause and predict: In the list [7, -1, 11, 32, 17, 19, 23, 29, 31, 37, 43], how many comparisons does linear search make to find the key 43?
Check your answer: 11. The key 43 is the last of the 11 elements, so every element has to be compared. This is the worst case, $n$ comparisons.
Binary search: halve the problem every time
Binary search uses the order of a sorted list. You compare the key with the middle element and one of three things happens: it matches (done), the middle is greater than the key (the key can only be in the first half), or the middle is smaller than the key (the key can only be in the second half). Either way, half the list is gone after a single comparison, and you repeat on what is left. The list may be sorted in ascending or descending order, and text works too if it is in alphabetical order.
The reason it needs a sorted list is the sentence "the key can only be in the first half". That is true only if everything before the middle is smaller. In an unsorted list the sentence is false and you would throw away the very half that holds the key.
The algorithm keeps three markers, first = 0, last = n - 1 and mid = (first + last) // 2, and repeats while first <= last. Three moves are possible inside the loop.
| Comparison | Meaning | Action |
|---|---|---|
numList[mid] == key |
Found | Report position mid + 1 and stop |
numList[mid] > key |
Key lies before the middle | last = mid - 1 |
numList[mid] < key |
Key lies after the middle | first = mid + 1 |
When the loop ends because first > last, the search area is empty and the answer is "Search unsuccessful". The list itself never changes. Only the three index markers move.
The floor division // matters. For 15 elements, mid = (0 + 14) // 2 = 7. For an even count such as 10 elements, mid = (0 + 9) // 2 = 4, the left one of the two middle elements, so the two halves hold 4 and 5 elements.
Worked example: binary search, key found. Let numList = [2, 3, 5, 7, 10, 11, 12, 17, 19, 23, 29, 31, 37, 41, 43] (15 elements, indexes 0 to 14) and search for 23.
| Iteration | first | last | mid | numList[mid] | Decision |
|---|---|---|---|---|---|
| 1 | 0 | 14 | 7 | 17 | 23 > 17, so first = 8 |
| 2 | 8 | 14 | 11 | 31 | 23 < 31, so last = 10 |
| 3 | 8 | 10 | 9 | 23 | Match: position 10 |
Three iterations were enough, whereas linear search would have needed 10 comparisons for this key. If the key is exactly the middle element (17 here), binary search finishes in 1 iteration, its best case.
Worked example: the worst case. Search the same list for 2, the very first element.
| Iteration | first | last | mid | numList[mid] | Decision |
|---|---|---|---|---|---|
| 1 | 0 | 14 | 7 | 17 | 2 < 17, so last = 6 |
| 2 | 0 | 6 | 3 | 7 | 2 < 7, so last = 2 |
| 3 | 0 | 2 | 1 | 3 | 2 < 3, so last = 0 |
| 4 | 0 | 0 | 0 | 2 | Match: position 1 |
The number of elements still in play went 15, 7, 3, 1: halved each time. That is 4 iterations, the maximum for this list.
Worked example: key not present. Search the same list for 9.
| Iteration | first | last | mid | numList[mid] | Decision |
|---|---|---|---|---|---|
| 1 | 0 | 14 | 7 | 17 | 9 < 17, so last = 6 |
| 2 | 0 | 6 | 3 | 7 | 9 > 7, so first = 4 |
| 3 | 4 | 6 | 5 | 11 | 9 < 11, so last = 4 |
| 4 | 4 | 4 | 4 | 10 | 9 < 10, so last = 3 |
Now first = 4 and last = 3, so first <= last is false and the loop ends: the search is unsuccessful after 4 iterations. A common wrong step is to stop at the third row because "9 lies between 7 and 11". The algorithm cannot know that until the search area is empty.
Program: binary search as a function. This version returns the index mid, or -1 when the key is absent, so the caller adds 1 to print a position.
| Code |
|---|
def binarySearch(list, key): |
first = 0 |
last = len(list) - 1 |
while first <= last: |
mid = (first + last) // 2 |
if list[mid] == key: |
return mid |
elif key > list[mid]: |
first = mid + 1 |
elif key < list[mid]: |
last = mid - 1 |
return -1 |
| Code |
|---|
list1 = [1, 3, 4, 5] |
pos = binarySearch(list1, 4) |
if pos != -1: |
print(4, "is found at position", pos + 1) |
else: |
print(4, "is not found in the list") |
| Output |
|---|
4 is found at position 3 |
Notice the two return conventions. Linear search returns index + 1 (a position) and None for failure. Binary search returns the raw mid (an index) and -1 for failure. Questions on this are really questions on reading the return statement.
Why it is so fast. After each iteration the area is halved, so the number of iterations in the worst case is $\lfloor \log_2 n \rfloor + 1$.
| Elements $n$ | Linear search (worst) | Binary search (worst) |
|---|---|---|
| 15 | 15 | 4 |
| 1,023 | 1,023 | 10 |
| 1,024 | 1,024 | 11 |
| 1,000,000 | 1,000,000 | 20 |
| $2^{30}$ (1,073,741,824) | 1,073,741,824 | 31 |
If the person you want sits at the exact middle of a sorted database of $2^{30}$ records, binary search finds them in 1 iteration, while linear search must compare about 537 million records first. That gap is why binary search ideas sit inside databases, routing tables in routers and compression codes, and why a dictionary or telephone directory is searched this way.
Beyond the textbook. For the exam: binary search needs a sorted list, and the sorting happens before the search. In reality: sorting a list costs more than one linear search over it, so binary search pays off when you search the same data many times, such as a contact book or a database index. For a single search in an unsorted list, plain linear search is the cheaper choice.
Pause and predict: A list of 10 sorted numbers is [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]. How many iterations does binary search need for the key 23, and what does the code return?
Check your answer: 3 iterations, returns index 5. First mid = (0 + 9) // 2 = 4 holds 16, and 23 > 16 so first = 5. Next mid = (5 + 9) // 2 = 7 holds 56, and 23 < 56 so last = 6. Then mid = (5 + 6) // 2 = 5 holds 23, a match. The function returns the index 5, and the printed position is 6.
Search by hashing: calculate, don't search
If you already knew which position a key must sit at, one comparison would settle the question. Hashing makes that possible. A hash function takes an element and produces an index; the elements are stored in a hash table at those indexes. To search, you apply the same function to the key, look at that one slot, and compare.
The simplest hash function is the remainder method, and it only works on numbers.
$$h(\text{element}) = \text{element} \bmod \text{size of the hash table}$$
In Python this is element % size. The remainder is the hash value, which is also the index in the table. Each slot of the table can hold only one item, and the table may be larger than the list.
Worked example: building a hash table. Store the list [34, 16, 2, 93, 80, 77, 51] in a hash table of 10 positions using h(element) = element % 10.
| Element | Calculation | Hash value (index) |
|---|---|---|
| 34 | 34 % 10 | 4 |
| 16 | 16 % 10 | 6 |
| 2 | 2 % 10 | 2 |
| 93 | 93 % 10 | 3 |
| 80 | 80 % 10 | 0 |
| 77 | 77 % 10 | 7 |
| 51 | 51 % 10 | 1 |
Placing each element at its index gives the table:
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| Value | 80 | 51 | 2 | 93 | 34 | None | 16 | 77 | None | None |
To search for 16, compute 16 % 10 = 6, look at index 6, and find 16: present, with a single comparison. To search for 46, compute 46 % 10 = 6, find 16 there, and 16 is not 46: absent, again with a single comparison. The time is the same whether the list has seven items or seven million, because the cost is one calculation plus one comparison.
Program: building a hash table and searching it.
| Code |
|---|
def hashFind(key, hashTable): |
if hashTable[key % 10] == key: |
return (key % 10) + 1 |
else: |
return None |
hashTable = [None, None, None, None, None, None, None, None, None, None] |
L = [34, 16, 2, 93, 80, 77, 51] |
for i in range(0, len(L)): |
hashTable[L[i] % 10] = L[i] |
print(hashTable) |
print(hashFind(16, hashTable)) |
print(hashFind(46, hashTable)) |
| Output |
|---|
[80, 51, 2, 93, 34, None, 16, 77, None, None] |
7 |
None |
The function returns (key % 10) + 1, the position, so 16 at index 6 is reported as position 7.
Collision. Hashing works perfectly only when every element maps to its own slot. Take the list [34, 16, 2, 26, 80] with h(element) = element % 10. Both 16 and 26 give the hash value 6, and one slot cannot hold two items. This clash is called a collision. Finding another slot for the element that lost the clash is called collision resolution. Several methods exist, but they are beyond this chapter, so the exam only asks you to recognise a collision and to know the term.
A hash function that maps every element of the list to a unique index is a perfect hash function, and with a perfect hash function collisions never occur. Real lists rarely allow that, so a perfect hash function is the ideal and collision resolution is the everyday reality.
Worked example: a bad table size. Use h(element) = element % 11 for [44, 121, 55, 33, 110, 77, 22, 66]. Every number is a multiple of 11, so every remainder is 0.
| Element | 44 | 121 | 55 | 33 | 110 | 77 | 22 | 66 |
|---|---|---|---|---|---|---|---|---|
| Element % 11 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
All eight elements fight for index 0, so seven collisions occur. The lesson is that the speed of hashing depends on how well the function spreads the data. A function that is cheap to compute but sends everything to one slot defeats the purpose.
Other hash functions exist apart from the remainder method: integer division, shift folding, boundary folding, mid-square, extraction and radix transformation. You only need to know their names. The time to compute a hash value does not depend on the number of items in the list, but it must be small enough that hashing still beats the other methods.
Beyond the textbook. For the exam: hashing is "one comparison", provided there is no collision. In reality: the dictionary and the set that you use in Python are built on hash tables, which is why checking key in my_dict stays fast even for huge dictionaries. They handle collisions behind the scenes, so the programmer never sees them.
Choosing the right search
| Situation | Best choice | Reason |
|---|---|---|
| Small list, no order | Linear search | No sorting needed and the work is tiny |
| Large list that is already sorted | Binary search | About $\log_2 n$ iterations |
| Many searches on the same large data, exact match on a number | Hash table | One comparison per search |
| Unsorted list, one single search | Linear search | Sorting first costs more than searching |
| Need to find the position in an alphabetical list of words | Binary search | Text sorted a to z can be halved like numbers |
Exam traps
| The trap | Why it's tempting | The truth |
|---|---|---|
| "Binary search works on any list." | It is the "fast" search, so it seems universal | The list must be sorted, ascending or descending |
| "For 10 elements the first mid is index 5." | Ten halved is five | mid = (first + last) // 2 = (0 + 9) // 2 = 4 |
| "Linear search returns the index." | Python lists use indexes | The program returns index + 1, the position |
| "Binary search's worst case for 15 elements is 3." | $\log_2 15$ is about 3.9 | It is 4 iterations, as $\lfloor \log_2 n \rfloor + 1$ |
| "Hashing always needs exactly one comparison." | The chapter says "one step" | Only if there is no collision at that slot |
| "A collision means the key is not found." | Both sound like failures | A collision happens while storing: two elements share one hash value |
| "The hash table must be as small as the list." | Saving space seems sensible | The table can be larger than the list |
| "Linear search finds the last duplicate." | The loop goes to the end | It stops at the first match |
60-second revision
| Topic | Lock this in |
|---|---|
| Key | The element being searched for |
| Linear search | Any list · best 1 · worst $n$ · returns index + 1 or None |
| Binary search | Sorted list only · mid = (first + last) // 2 · numList[mid] > key gives last = mid - 1 · else first = mid + 1 |
| Binary stop rule | Stop when found, or when first > last |
| Binary worst case | $\lfloor \log_2 n \rfloor + 1$ iterations (15 elements: 4) |
| Hash function | element % size of table gives the index |
| Hash search | One comparison, if no collision |
| Collision | Two elements, same hash value |
| Collision resolution | Finding another slot for the clashing element |
| Perfect hash function | Every element gets a unique index, so no collision |
Where this chapter connects
- Previous: Sorting arranges a list in order, which is exactly the condition binary search needs.
- Next: Understanding Data moves from searching a list to collecting, storing and summarising data.
- Same ideas elsewhere: Stack and Queue are other list based data structures built on the same Python list operations.
🎯 Practice MCQs
First 3 questions free · create a free account to unlock the rest — answers & explanations included, no payment needed
Q1. Which statement about linear search is correct?
▸ Show answer & explanation
Answer: B
Linear search checks elements one after another and works on sorted or unsorted lists. Option A is a requirement of binary search, option C describes binary search and option D describes hashing.
Q2. A list has n elements and the key is not present. How many comparisons does linear search make?
▸ Show answer & explanation
Answer: C
The whole list must be checked before the search can declare failure, so n comparisons are made. This is the worst case, the same as when the key is the last element.
Q3. A function f(lst, key) loops over every index i of lst from 0 to len(lst) - 1 and returns i + 1 as soon as lst[i] equals key. If the loop finishes without a match, it returns None. What is printed by print(f([10, 20, 30, 40], 30))?
▸ Show answer & explanation
Answer: B
The key 30 is at index 2, and the function returns index + 1, which is 3. Option A is the index of 20, option C is the value rather than the position, and option D would be returned only if 30 were absent.
🔒 12 more practice MCQs
Create a free account to unlock every MCQ in this chapter — answers and explanations included. No payment needed.
Already registered? Just log in and they'll all appear here.
Q4. For the sorted list [2, 3, 5, 7, 10, 11, 12, 17, 19, 23, 29, 31, 37, 41, 43], how many iterations does binary search need to find the key 23?
▸ Show answer & explanation
Answer: C
The first mid is index 7 (value 17), and 23 is larger so first becomes 8. The next mid is index 11 (value 31), and 23 is smaller so last becomes 10. The third mid is index 9 (value 23), a match. So 3 iterations.
Q5. In binary search, if numList[mid] is greater than the key, which assignment is made?
▸ Show answer & explanation
Answer: B
A larger middle value means the key can only be in the first half, so the upper marker last moves to mid - 1. Option A is used when the middle value is smaller than the key. Options C and D do not follow from the comparison.
Q6. Which condition must hold for binary search to give a correct answer?
▸ Show answer & explanation
Answer: C
Discarding half the list is valid only when the order of the elements tells you which half can hold the key. The number of elements, the sign of the numbers and hashing have nothing to do with it.
Q7. Binary search for the key 9 is done on the sorted list [2, 3, 5, 7, 10, 11, 12, 17, 19, 23, 29, 31, 37, 41, 43]. After the iteration in which mid holds 10, what are the values of first and last, and what happens next?
▸ Show answer & explanation
Answer: A
The mids are 17, 7, 11 and 10 in four iterations. At mid index 4 (value 10) the key 9 is smaller, so last = 4 - 1 = 3 while first stays 4. Since first is greater than last, the loop ends and the search is unsuccessful.
Q8. What is the largest number of iterations binary search can need on a sorted list of 1023 elements?
▸ Show answer & explanation
Answer: B
The worst case is the floor of log2(1023) plus 1. Since log2(1023) is just under 10, its floor is 9 and the answer is 10. Option C would be correct for 1024 elements, and option D is the figure for linear search.
Q9. Using h(element) = element % 10, which element would be stored at index 3 of the hash table?
▸ Show answer & explanation
Answer: C
93 % 10 = 3. The other remainders are 34 % 10 = 4, 16 % 10 = 6 and 80 % 10 = 0.
Q10. A hash table of size 7 uses h(x) = x % 7. At which index will the element 47 be placed?
▸ Show answer & explanation
Answer: B
47 = 6 x 7 + 5, so the remainder, and hence the index, is 5. The remainder of a division by 7 can only be from 0 to 6, so 47 itself cannot be an index.
Q11. Assertion (A): In hashing, the time to search for a key does not depend on the number of items in the list. Reason (R): The hash function calculates the index of the key directly, so only one comparison is needed when there is no collision.
▸ Show answer & explanation
Answer: A
The index is computed in the same time however long the list is, and then one comparison settles the matter. R is the reason A holds.
Q12. With h(element) = element % 10, which pair of elements causes a collision?
▸ Show answer & explanation
Answer: B
16 % 10 and 26 % 10 are both 6, so both want index 6. In option A the remainders are 4 and 3, and in option C they are 0 and 1. In option D the remainders are 5 and 6.
Q13. Match List I with List II. List I: (i) Perfect hash function (ii) Collision (iii) Collision resolution (iv) Remainder method. List II: (p) Two elements get the same hash value (q) Finding another slot for a clashing element (r) element % size of table (s) Every element gets a unique index
▸ Show answer & explanation
Answer: A
A perfect hash function gives unique indexes, a collision is two elements with one hash value, collision resolution is finding another slot, and the remainder method is element % size of table. The other options swap collision with collision resolution or the remainder method with the perfect hash function.
Q14. Consider: (1) hashTable is a list of five None values (2) for each x in the list [12, 8, 20, 3], the statement hashTable[x % 5] = x is executed (3) print(hashTable). What is printed?
▸ Show answer & explanation
Answer: A
12 % 5 = 2, 8 % 5 = 3, 20 % 5 = 0 and 3 % 5 = 3. The elements 8 and 3 collide at index 3, and the later one (3) overwrites 8. Index 4 is never used, so it stays None. Option B keeps the earlier element, and options C and D ignore the remainders.
Q15. A sorted database holds 1073741824 records (2 to the power 30) and the record wanted is exactly at the middle position. Which is true?
▸ Show answer & explanation
Answer: A
The first mid of binary search is the middle record, so it succeeds at once. Linear search must compare every record before the middle one as well, which is about half a billion records.
📊 Previous-Year Questions
Practise with real CUET Computer Science previous-year papers — every question solved, with the correct answer and a step-by-step explanation.
View solved CUET PYQ papers →Ready to drill Computer Science?
Unlock all MCQs, chapter tests, mocks & PYQs for ₹199/year.
Get UniDrill Pro