Basic Algorithms and Data Structures
The handful of classic patterns every programmer learns to recognize — linear and binary search, simple sorts, and the stack and queue
So far we have used data structures (lists, sets, maps) and written ad-hoc loops. This page introduces the classic algorithms and structures that show up in real software again and again. We will not get formal — no Big-O notation lectures — but we will develop intuition for which idea to reach for and why it works.
Linear search
The simplest possible search: look at each element until you find what you want, or run out.
Linear search:
- Works on any collection — sorted, unsorted, anything iterable.
- In the worst case, looks at every element. Doubling the collection roughly doubles the time.
- Is exactly what
List.contains(...)andList.indexOf(...)do internally for a plainArrayList.
For small lists, this is fine. For 10 million elements, repeatedly, it is too slow. Then you need…
Binary search
If the collection is sorted, you can do vastly better. Look at the middle element. If it matches, done. If your target is smaller, search the left half. Otherwise the right half. Each step eliminates half of what is left.
A list of 1 million elements is fully searched in 20 comparisons (2^20 ≈ 1M). A list of 1 billion: about 30 comparisons.
The cost of binary search: the input must be sorted. So sorting is itself worth investing in.
A simple sort: selection sort
Sorting is one of the most-studied subjects in CS. The fancy ones (quick sort, merge sort) are clever; the slow ones are easy to understand. Here is selection sort: on each pass, find the smallest remaining element and put it in the next position.
Selection sort takes time proportional to the square of the list
length. For 10,000 elements, fine. For 10,000,000 elements, way too
slow — for that you'd use the JDK's built-in Arrays.sort, which
is O(n log n).
The general rule: you almost never write a sort in production
Java. You call Collections.sort(list) or Arrays.sort(arr).
But writing one once in your life makes the cost of sorting
intuitive forever.
Two abstract data types: stack and queue
A data structure is how something is stored. An abstract data type (ADT) is what operations it supports. Two ADTs you will meet everywhere:
Stack — Last In, First Out (LIFO)
You push items on top; you pop them off the top.
Stacks model call frames (we saw it on the stack-and-heap page!), undo history, brace matching in parsers, depth-first search.
Queue — First In, First Out (FIFO)
You enqueue items at the back; you dequeue from the front. Like a line at a coffee shop.
Queues model task scheduling, message buffers, breadth-first search.
Java has classes for both — Deque (often used as a stack via
ArrayDeque) and Queue (often LinkedList or ArrayDeque):
Knowing what to reach for
A working programmer's mental table of first-resort tools:
| Problem | First-resort tool |
|---|---|
| "Is X in this collection?" | HashSet.contains |
| "What value goes with key K?" | HashMap.get |
| "What's the smallest element?" | sort once, take first; or use a priority queue |
| "Process items in arrival order" | Queue |
| "Process items in last-in-first-out order" | Deque as a stack |
| "Find an item in a sorted list" | binary search |
| "Sort a list" | Collections.sort |
| "I'll iterate a million times — does `contains` need to be fast?" | use a Set, not a List |
You will, of course, run into problems that don't fit this table. But this table covers an astonishing fraction of day-to-day code.
Why does binary search require a sorted input?
Sorting makes elements take less memory
The algorithm uses the index parity
Each step compares the target to the middle element and then discards an entire half of the remaining range based on the ordering; without an ordering, you cannot rule out a half
Java's binary search throws an exception on unsorted input
In a stack (LIFO), the last item you pushed is…
Lost forever
The next-to-last to come out
The first one to come out when you pop
Sorted to the bottom
A program calls list.contains(x) inside a tight loop 1,000,000 times on a 1,000,000-element ArrayList. Which change is most likely to fix the resulting slowness?
Replace the loop with recursion
Sort the list first and keep using contains
Replace the ArrayList with a HashSet so each contains call runs in roughly constant time
Re-create the list with a smaller capacity
A challenge
Implement Reverser.reverse(String s) using a Deque<Character> as a stack: push every character, then pop them all back into a StringBuilder.
Main.java is provided. Expected output (exact):
!olleh
You now have a vocabulary for the dozen most common patterns. The last conceptual page widens the lens one more time: how is a whole program organized?
Writing Maintainable Software
Code is read far more often than it is written — the practices that make programs survive contact with humans (including future you)
Software Organization
How classes, packages, and layers come together to form a real program — and the mental models that scale to large systems