Algorithm fundamentals
An algorithm is a finite, unambiguous procedure with defined inputs and outputs. This lesson lists the properties every algorithm must have and teaches the two paper tools you use to understand one: the dry run and the tracing table.
Learning goals
- List the properties that make a procedure an algorithm
- State preconditions and postconditions for a method
- Read and write pseudocode
- Trace code by hand with a tracing table
Prerequisites
Algorithm properties
A procedure counts as an algorithm when it has: input (zero or more values), output (at least one result), finiteness (it stops), definiteness (every step is precise) and effectiveness (every step can be done by a machine in finite time).
“Make the best coffee” is not an algorithm: the steps are not definite. “Boil 200 ml of water, add 10 g of grounds, wait 4 minutes, pour” is.
| Property | Question it answers |
|---|---|
| Input | What does it receive? |
| Output | What does it produce? |
| Finiteness | Does it always stop? |
| Definiteness | Is every step unambiguous? |
| Determinism | Does the same input give the same steps and result? |
| Effectiveness | Can each step actually be executed? |
Finiteness
An algorithm must terminate on every valid input. Loops need a measure that moves toward the exit condition on every iteration, such as an index that grows toward n or a value that shrinks toward 0.
A common bug is updating the wrong variable, or updating it only on some paths, so the loop never ends.
1// terminates: i grows toward n on every iteration2for (int i = 0; i < n; i++) { work(); }34// terminates: n halves each time and is an int, so it reaches 05while (n > 0) { n /= 2; }67// does NOT terminate when x is 0 or negative: x never reaches 10 from below8int x = 1;9while (x != 10) { x -= 1; } // bug: should have been x += 1
Determinism
A deterministic algorithm performs the same steps and returns the same result every time it is given the same input. Most algorithms in this course are deterministic, which makes them easy to test.
Randomness, threads and reading the clock introduce non-determinism. Keep such sources behind a parameter (a seed, a clock object) so that tests can control them.
1Random rnd = new Random(42); // fixed seed: same sequence every run2int a = rnd.nextInt(100);3int b = new Random(42).nextInt(100); // a == b4// new Random() without a seed differs on every run, so tests become flaky
Definiteness
Each step must have one precise meaning. “Process the items quickly” is not definite; “for each item in index order, add its price to total” is. Definiteness also means the order of steps is fixed, and every branch has a stated condition.
Typical sources of ambiguity: unclear tie-breaking (“pick the largest” when two are equal), unspecified order of iteration, and undefined behaviour for edge inputs such as an empty array.
Preconditions and postconditions
A precondition is what must be true when a method is called; a postcondition is what is guaranteed when it returns. If the caller breaks a precondition, the method owes nothing. If the method breaks a postcondition, the method has a bug.
In Java, check cheap preconditions with exceptions at public boundaries, and use assert for internal conditions you believe are always true.
1/** Precondition: lo <= hi, arr sorted. Postcondition: returns first index with arr[i] >= target, or arr.length. */2static int lowerBound(int[] arr, int target) {3Objects.requireNonNull(arr); // public boundary: throw on misuse4int lo = 0, hi = arr.length;5while (lo < hi) {6int mid = lo + (hi - lo) / 2;7if (arr[mid] < target) lo = mid + 1; else hi = mid;8}9assert lo == arr.length || arr[lo] >= target; // postcondition (run with java -ea)10return lo;11}
Pseudocode
Pseudocode is a language-neutral outline of an algorithm: precise enough to implement, loose about syntax. Use it to think before coding and to compare the idea with the Java.
Conventions used on this site: indentation shows nesting, ← is assignment, arrays are 0-based, and return ends the procedure.
1LINEAR-SEARCH(A, target)2for i ← 0 to length(A) - 13if A[i] = target4return i5return -167// Java8static int linearSearch(int[] a, int target) {9for (int i = 0; i < a.length; i++) if (a[i] == target) return i;10return -1;11}
Flowcharts
A flowchart draws an algorithm as boxes and arrows: rounded boxes for start and end, rectangles for actions, diamonds for decisions. They are useful for explaining branching logic to non-programmers, and clumsy for loops over data, which is why this course uses animations and tracing tables instead.
Text form of the flowchart for “is n even?”: START → read n → decision [n % 2 == 0] → yes: print “even”, no: print “odd” → END.
| Shape | Meaning |
|---|---|
| Rounded box | Start or end |
| Rectangle | Action or assignment |
| Diamond | Yes/no decision (two exits) |
| Parallelogram | Input or output |
| Arrow | Order of execution |
Dry runs
A dry run executes code by hand with concrete values, without a computer. It finds logic errors early and builds the habit of thinking in program states. Pick a small input, write down each variable, and update them one statement at a time.
Step through the animation below with a different array each time. Each frame shows the variables a dry run would track, the condition just evaluated, and the highlighted line.
Dry run: sum the even values
Interactive · edit the inputint[] arr · length 6
Every variable gets a column in the tracing table below. Step through and fill in one row per statement.
1public static int sumEvens(int[] arr) {2int sum = 0;3for (int i = 0; i < arr.length; i++) {4if (arr[i] % 2 == 0) {5sum += arr[i];6}7}8return sum;9}
Live state
sum
0
n
6
Execution trace (dry run table)Show 18 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | sum=0 n=6 | Every variable gets a column in the tracing table below. Step through and fill in one row per statement. |
| 2 | L3 | i=0 sum=0 | i = 0 is below 6, so the body runs. |
| 3 | L4 | i=0 arr[i]=3 sum=0 | 3 % 2 is 1, not 0, so skip it. In Java the remainder keeps the sign of the dividend, so odd negatives give -1, not 1. |
| 4 | L3 | i=1 sum=0 | i = 1 is below 6, so the body runs. |
| 5 | L4 | i=1 arr[i]=8 sum=0 | 8 % 2 is 0, so 8 is even. |
| 6 | L5 | i=1 arr[i]=8 sum=8 | sum becomes 8. |
| 7 | L3 | i=2 sum=8 | i = 2 is below 6, so the body runs. |
| 8 | L4 | i=2 arr[i]=-2 sum=8 | -2 % 2 is 0, so -2 is even. |
| 9 | L5 | i=2 arr[i]=-2 sum=6 | sum becomes 6. |
| 10 | L3 | i=3 sum=6 | i = 3 is below 6, so the body runs. |
| 11 | L4 | i=3 arr[i]=7 sum=6 | 7 % 2 is 1, not 0, so skip it. In Java the remainder keeps the sign of the dividend, so odd negatives give -1, not 1. |
| 12 | L3 | i=4 sum=6 | i = 4 is below 6, so the body runs. |
| 13 | L4 | i=4 arr[i]=10 sum=6 | 10 % 2 is 0, so 10 is even. |
| 14 | L5 | i=4 arr[i]=10 sum=16 | sum becomes 16. |
| 15 | L3 | i=5 sum=16 | i = 5 is below 6, so the body runs. |
| 16 | L4 | i=5 arr[i]=5 sum=16 | 5 % 2 is 1, not 0, so skip it. In Java the remainder keeps the sign of the dividend, so odd negatives give -1, not 1. |
| 17 | L3 | i=6 sum=16 | i = 6 is not below 6: the loop ends. |
| 18 | L8 | sum=16 return=16 | return 16. The tracing table now lists every state change, which is exactly what you would write on paper. |
Tracing tables
A tracing table has one column per variable (and one for the condition) and one row per step. It is the written form of what the animation's trace table shows. Open “Execution trace” under the player to see every step as a row.
Dry run of sumEvens on [3, 8, -2, 7]:
| Step | i | arr[i] | arr[i] % 2 == 0 | sum |
|---|---|---|---|---|
| start | - | - | - | 0 |
| 1 | 0 | 3 | false | 0 |
| 2 | 1 | 8 | true | 8 |
| 3 | 2 | -2 | true | 6 |
| 4 | 3 | 7 | false | 6 |
| end | 4 | - | i < 4 is false | 6 |
Practice ladder
Now try it yourself
Easy
- Write pseudocode for finding the maximum of an array
- Dry-run a loop that sums 1 to 5 in a tracing table
Medium
- Find the bug that makes
while (x != 10) x -= 1never end, and fix it - Write pre- and postconditions for a method that removes duplicates from a sorted array
Advanced
- Write a loop whose termination depends on a non-trivial measure (for example Euclid's gcd) and prove it ends
Key takeaways
- An algorithm needs input, output, finiteness, definiteness and effectiveness.
- Preconditions say what callers must provide; postconditions say what the method guarantees.
- Dry runs and tracing tables turn code into a sequence of states you can check.