Computation starts with a question
You have four books on a shelf and want to know where The Hobbit is. You could look along the shelf, read each title and stop when you find it. That simple activity contains the ingredients of computation: information, a precise question, a sequence of steps and a result.
Computation transforms information according to rules. The information can describe numbers, text, pictures, movements or relationships. A computer performs those rules using a physical machine; a person can carry out the same small computation with pencil and paper. Computer science studies how to represent problems, construct and analyse procedures, organise systems and understand what computation can and cannot achieve.
Programming is one way to express a procedure so a machine can execute it. Computer science also asks whether the procedure answers the right question, whether it always finishes, how much work it requires and how it fits into a larger system. These questions remain relevant when the programming language changes.
A question made precise
Our catalogue is an ordered list: ["Dune", "Foundation", "The Hobbit", "Dune"]. The question is: what is the index of the first title exactly equal to the target? Count positions from zero. For "The Hobbit", the answer is 2. For "Dune", it is 0. For "Solaris", we choose -1 to mean “absent”.
Notice the decisions already made: order matters, matching is exact, duplicates are possible, and missing books have an explicit result. “Find a book” did not specify any of those. Much of good computing begins with resolving such ambiguities.
Does counting the books qualify as computation? State its input and output.
Worked solution
Yes. Input: the finite catalogue. Output: its number of entries. Increment a counter once per entry.
Specify inputs, outputs and assumptions
A problem specification states the required relationship between inputs and outputs. An algorithm is a procedure for achieving it. A program implements a procedure in a language with defined execution rules. There can be many algorithms for one problem and many programs implementing one algorithm.
| Part | Our search contract |
|---|---|
| Input | A finite ordered list of titles and a target title. |
| Assumptions | Titles are strings. The list does not change during this search. Equality is exact and case-sensitive. |
| Output when present | The smallest index whose title equals the target. |
| Output when absent | -1, including when the list is empty. |
| Side effects | The search does not modify the list. |
The assumptions are preconditions: what must hold before the procedure runs. The promised output and unchanged input are postconditions: what must hold after it returns. Together they make a contract. Later modules will use contracts for data structures, network protocols and database transactions.
Here -1 is a sentinel, a chosen value representing absence. Python also permits negative indexing: books[-1] reads the last book. Check whether a result is -1 before using it as an index. A future implementation might return None instead; that would be a different contract.
A library user might expect “dune” to match “Dune”. That would require a new matching rule, for example case-insensitive comparison. Do not quietly change the meaning while claiming the original contract still holds.
What result is required for an empty catalogue? Is index 0 a missing result?
Worked solution
Return -1 for an empty catalogue. Index 0 is the first valid position in a nonempty catalogue.
Describe an algorithm and trace its state
Linear search checks items in their given order. In pseudocode, which expresses the steps without depending on a particular programming language:
FOR each position i, from first to last:
IF title at i equals target:
RETURN i
RETURN -1
The final return happens only after all items have been examined. The return inside the loop ends the entire search immediately. That immediate stop gives us the first matching index even when a title occurs twice.
The procedure has state: the information needed to describe where it is in its execution. Here the changing state is the current position and, when we measure work, the number of comparisons. A trace records that state step by step.
| Position | Title | Equals The Hobbit? | Next action |
|---|---|---|---|
| 0 | Dune | No | Advance |
| 1 | Foundation | No | Advance |
| 2 | The Hobbit | Yes | Return 2 |
The fourth title is never inspected in this run. Predict a trace before running code: doing so forces you to distinguish what you expect from what the program actually does.
Try three experiments. Search for Dune and explain why index 3 is never reached. Search for Solaris and count the comparisons. Select the empty catalogue and explain why returning -1 requires no title comparisons. The demonstration shows the final “list exhausted” step separately; that step is not a title comparison.
Trace a search for Foundation. Which positions are inspected?
Worked solution
Inspect 0, then 1. Return 1 after two comparisons; do not inspect 2 or 3.
Why it is correct, why it finishes, what it costs
Examples show particular executions. An argument about the procedure covers every input satisfying the contract. We will develop formal proofs later; a short reasoning pattern is enough here.
Correctness: what stays true?
Just before we inspect position i, none of the earlier positions contains the target. This is a loop invariant, a statement preserved as the loop advances. It is initially true because there are no earlier positions at index 0. If the current title does not match, adding that title to the inspected prefix preserves the statement. If it matches, the invariant tells us that no earlier match exists, so returning i satisfies the contract. If the list is exhausted, every title was unequal, so returning -1 satisfies the contract.
Termination: what makes progress?
For a list with n entries, each unsuccessful iteration advances one position. The number of uninspected entries decreases by one and cannot decrease forever below zero. A match ends the search even sooner. A finite list that remains unchanged therefore guarantees termination.
Cost: count a defined operation
Choose one title-equality check as the operation to count. For a nonempty list, a first-position match costs one comparison. A last-position match or an absent target costs n comparisons. An empty list costs zero comparisons. Doubling the number of entries doubles the worst-case comparison count.
This count is a model, not elapsed time. Comparing two long strings may require examining many characters; a title comparison is not necessarily constant-time when title length varies. The computer, implementation and data also affect seconds on a clock. Module 05 develops these distinctions and introduces Big O. Here, specify the operation and count it accurately.
Does it return the required answer? Does it finish? How much work does it do? A fast wrong answer is still wrong, and a correct answer promised by an infinite loop never arrives.
Why is the empty catalogue compatible with the invariant argument?
Worked solution
There are no earlier matches and no entries left to inspect. Every entry is a nonmatch because there are no entries, so -1 is correct.
Abstraction and the layers of a computer
An abstraction exposes the properties needed for a task while hiding other details. Treating the catalogue as an ordered list lets us reason about positions without knowing how a particular machine stores strings. Treating search as a function lets another part of a program use the result without repeating the search steps.
An abstraction must retain what matters. If we replace the ordered list with a collection that has no meaningful order, “first index” loses its meaning. If we keep only titles and discard authors, we cannot answer an author query without adding information.
- Problem and applicationA user asks where a title appears in the catalogue.
- Algorithm and programLinear search describes the steps; Python expresses them as executable code.
- Language implementationThe Python runtime executes operations on lists and strings.
- Operating systemManages processes, memory and access to files or devices.
- HardwareCPU instructions operate on data stored in memory.
When you run a script, the CPU does not directly understand a book title or the English phrase “find the first match”. A language implementation and machine instructions connect that meaning to physical operations. You can learn to reason about the search before learning all those mechanisms.
The same separation helps with faults. A requirement might ask for the wrong matching rule; the algorithm might skip index 0; the program might place return -1 inside the loop; the environment might fail to open a file. Identify the responsibility that failed before changing the code.
Can a titles-only abstraction answer who wrote each book? Why?
Worked solution
Not in general. Author information was not retained. Add it to the representation or obtain it from another source.
Common misconceptions
| Misconception | What to check instead |
|---|---|
| “Computation is only arithmetic.” | Searching text and following a route also transform information according to rules. |
| “An algorithm is a Python file.” | Distinguish the procedure from its language-specific implementation. |
| “It worked once, so it is correct.” | Check edge cases and reason about all valid inputs. |
| “Index zero means not found.” | Zero is a valid position. Our missing sentinel is -1. |
| “Four titles means four comparisons.” | An early match stops the search; the actual cost depends on the target. |
| “All bugs belong in the algorithm.” | Separate requirements, procedure, implementation and environment. |
Set up the labs
Use Python 3.11 or later, a text editor and a terminal. The labs use only Python's standard features; no packages or datasets are needed. Download each script below or copy its code into a file with the same name. In a terminal opened in that folder, run python lab1_trace.py. On Windows, py -3 lab1_trace.py is another option. On systems where Python is named python3, use that command.
A quoted value such as "Dune" is a string; square brackets hold a list. for repeats the indented block. enumerate supplies an index and an item. if selects a block when a condition is true. == compares values; = assigns a value to a name. Indentation is part of Python's syntax. The labs introduce only the notation needed here; Module 03 teaches programming systematically.
Lab 1 — Read an execution trace
Goal: connect pseudocode, state and actual execution. Time: 30 minutes. First predict which lines will print. Then run the script and compare your prediction with the captured output.
"""Module 01, Lab 1: trace a search, one comparison at a time."""
books = ["Dune", "Foundation", "The Hobbit", "Dune"]
target = "The Hobbit"
found = -1
for index, title in enumerate(books):
print(f"inspect {index}: {title}")
if title == target:
found = index
break
print(f"result: {found}")
inspect 0: Dune
inspect 1: Foundation
inspect 2: The Hobbit
result: 2
found = -1 sets the absent result before searching. break exits the loop after a match; the final print still runs. The f before a quoted string lets the print include values inside braces.
- Change the target to Dune, then Solaris. Write the predicted traces before running.
- Set
books = []. Explain why the loop body does not run. - Remove
breakand search for Dune. Why does the result become 3? Which part of the contract fails? - Restore the original code. Explain the difference between “exit this loop” and “return from this function”.
Lab discussion
Dune inspects only index 0; Solaris inspects all four indices and keeps the result -1. An empty list produces only result: -1. Without the break, both occurrences of Dune update found, so the last occurrence wins. That violates the first-match contract. A break leaves the loop; a return leaves the function that contains it.
Lab 2 — Turn the contract into checks
Goal: test empty, missing, boundary and duplicate cases. Time: 35 minutes. A def block defines a function. A return supplies its result and ends that call. assert stops the script if its condition is false. Run normally, without Python's -O option, which disables assertions.
"""Module 01, Lab 2: make a contract executable with examples."""
def find_first(items, target):
"""Return the first matching index, or -1 if target is absent."""
for index, item in enumerate(items):
if item == target:
return index
return -1
cases = [
([], "Dune", -1),
(["Dune"], "Dune", 0),
(["Dune"], "Solaris", -1),
(["Dune", "Solaris"], "Solaris", 1),
(["Dune", "Solaris", "Dune"], "Dune", 0),
]
for items, target, expected in cases:
actual = find_first(items, target)
assert actual == expected, (items, target, expected, actual)
print(f"{items!r}, {target!r} -> {actual}")
print("5 cases passed")
[], 'Dune' -> -1
['Dune'], 'Dune' -> 0
['Dune'], 'Solaris' -> -1
['Dune', 'Solaris'], 'Solaris' -> 1
['Dune', 'Solaris', 'Dune'], 'Dune' -> 0
5 cases passed
- Explain why the five cases exercise different parts of the contract.
- Add
(["Dune"], "dune", -1)to confirm exact, case-sensitive matching. - Move
return -1inside the loop, immediately after theifblock. Predict which checks will fail or which result will change, then run. - Repair the function. Add a check that the input list is unchanged after a call.
Lab discussion
The cases cover an empty list, the first position, an absent target, the last position and duplicate titles. With return -1 inside the loop, an empty list falls through and returns None; the first assertion fails. A later-position match is also missed because the search returns after its first nonmatch. To check mutation, save before = items.copy(), call the function and check assert items == before. Passing these examples is useful evidence, but the invariant argument is what covers every valid input.
Lab 3 — Measure work, not clock speed
Goal: distinguish input size from work in a particular run. Time: 35 minutes. This script returns a pair: result and comparison count. range(size) produces integers from zero up to, but excluding, size; list collects them. The same equality-search procedure applies to numbers. += 1 increases a counter by one.
"""Module 01, Lab 3: count comparisons instead of timing the computer."""
def search_with_cost(items, target):
comparisons = 0
for index, item in enumerate(items):
comparisons += 1
if item == target:
return index, comparisons
return -1, comparisons
for size in [0, 1, 4, 8, 16]:
items = list(range(size))
missing_index, missing_cost = search_with_cost(items, -1)
first_index, first_cost = search_with_cost(items, 0)
print(f"n={size:2}: absent={missing_cost:2}, first={first_cost:2}")
assert missing_index == -1 and missing_cost == size
assert first_index == (0 if size else -1)
assert first_cost == (1 if size else 0)
n= 0: absent= 0, first= 0
n= 1: absent= 1, first= 1
n= 4: absent= 4, first= 1
n= 8: absent= 8, first= 1
n=16: absent=16, first= 1
- Predict both counts for a list of length 32.
- Add a last-position target using
size - 1. For size 0, the target remains absent. - Search for the middle position of a 16-entry list: target 8. Why are there nine comparisons?
- State the best and worst comparison counts for a nonempty list. Explain why elapsed time alone would not establish those formulas.
Lab discussion
For size 32, absent costs 32 comparisons and first costs 1. Last costs size for a nonempty list and 0 for an empty one. Index 8 is the ninth position because counting begins at zero. Best is 1 and worst is n for n greater than zero. Timing depends on many machine and data details; these exact counts follow from the procedure.
Exercises with worked solutions
Try each question before opening its solution. ★ means a direct application; ★★ combines ideas; ★★★ asks you to change the contract or construct an argument.
Write a contract for counting occurrences of a target title, including an empty list and duplicates.
Worked solution
Input: a finite, unchanged list of strings and a target string. Output: the number of entries exactly equal to the target, a nonnegative integer. Empty input returns 0. Duplicates each contribute one. The list is not modified.
Search [A, B, A] for A under our first-match contract. List inspected indices and the returned result. How would a last-match contract differ?
Worked solution
Inspect only 0 and return 0. A last-match search must examine the remaining entries; a full forward scan updating the result at each match would return 2 after three comparisons.
A search returns -1 immediately after the first nonmatch. Give a failing input and explain the repair, including empty input.
Worked solution
[A, B] with target B fails: A is unequal, but B was never examined. Move the absent return after the loop so it runs only when all entries have been checked. This also returns -1 for an empty list instead of falling through to None.
A list has 12 entries. How many comparisons find a first match at index 7? How many establish absence? What if the list is empty?
Worked solution
Index 7 requires 8 comparisons (indices 0–7). Absence requires 12. An empty list requires 0. These count equality checks, not all machine instructions.
Give the initialisation, preservation and exit arguments for: before inspecting i, no earlier index contains the target.
Worked solution
Initially i=0 has no earlier positions. After a nonmatch at i, all positions before i+1 are nonmatches. On a match, no earlier match exists, so i is minimal. On exhaustion, all entries are nonmatches. Finite length and advancing positions establish termination separately.
You replace the catalogue by its entry count alone. Which queries can it answer: how many entries, where is Dune, or which title is first?
Worked solution
Only the number of entries. Many different catalogues have the same count, so the count cannot recover either title identity or order. An abstraction is appropriate relative to the question being asked.
Specify and implement a first-match search for ASCII titles ignoring letter case. Which checks should change?
Worked solution
Require ASCII strings and compare item.lower() == target.lower() inside the same loop; keep the absent return after the loop. Now [Dune] with target dune returns 0. Empty, absent and duplicate tests remain necessary. The existing exact-match contract has changed; text matching beyond ASCII needs more careful rules.
def find_first_ascii_ignore_case(items, target):
for index, item in enumerate(items):
if item.lower() == target.lower():
return index
return -1
assert find_first_ascii_ignore_case(["Dune"], "dune") == 0
assert find_first_ascii_ignore_case([], "dune") == -1Classify these faults: the requested matching rule is wrong; pseudocode skips index 0; return -1 is indented inside the loop; the script file cannot be opened.
Worked solution
Respectively: specification, algorithm, implementation and environment. Check the contract with its user, repair the procedure, repair the code's control flow, or check the command and file path. A fault can involve multiple layers, but this distinction guides the first investigation.
Self-check quiz
Choose an answer to see feedback. The score counts answered questions; reset to try again. If scripts are disabled, open the answer key below.
Which statement specifies a problem rather than an algorithm?
What does our search return on an empty list?
Where is the first Dune in [Dune, Foundation, Dune]?
How many equality comparisons establish absence in 9 entries?
Which fact ensures this search terminates?
What does passing five tests establish?
Why check a returned -1 before indexing a Python list?
What must an ordered-list abstraction retain?
Answer key
- A — It describes the required result without prescribing the steps.
- B — The contract uses -1 for absence, including an empty list.
- C — Positions start at zero, and the algorithm stops at the first match.
- B — All nine entries must be compared. The final return is not a title comparison.
- A — Remaining work decreases to zero; the target may be absent.
- C — Tests provide evidence about cases; the invariant argument covers valid inputs generally.
- B — Our sentinel convention and Python's indexing rule have different meanings.
- A — First-match search needs order and item equality, not those physical details.
Guided reading
Read the official Python introduction to lists and the sections on for statements, range, break and defining functions. Focus on the constructs used in the labs; you can skip the other control-flow features for now.
Write down why an empty list causes zero loop iterations, why range excludes its endpoint and what return does. Then explain how those language rules support our search algorithm. The distinction between a language rule and a problem requirement is the purpose of this reading.
Review and the next step
You can now turn a vague search request into a contract, trace a procedure, explain its first-match property, show why it terminates and count its comparisons. You have also located that computation within the layers of a running computer.
Close the page and reproduce the pseudocode from memory. Trace an empty list, a duplicate match and an absent title. Explain which assumption makes the termination argument work. If one explanation is uncertain, revisit that section before checking off your last session.
The next module is Representing information: how bits encode integers and text, and why some numbers cannot be represented exactly. The course overview links to all 14 modules.
Key terms
| Term | Meaning in this module |
|---|---|
| Computation | Transformation of information according to rules. |
| Specification | The required relationship between input and output. |
| Algorithm / program | A procedure / an implementation in a programming language. |
| State / trace | Current execution information / a sequence recording it. |
| Contract | Preconditions and postconditions for an operation. |
| Invariant | A property preserved at a chosen point through iterations. |
| Termination | The procedure eventually finishes. |
| Abstraction | A model exposing relevant properties while hiding details. |
| Sentinel | A chosen value representing a special outcome such as absence. |