Why learn DSA
Data structures and algorithms decide whether a program answers in milliseconds or never finishes. This lesson explains the vocabulary of problems, algorithms and contracts, and why correctness and efficiency are separate questions.
Learning goals
- Distinguish a problem from the algorithms that solve it
- State an input-output contract precisely
- Explain why a correct solution can still be useless at scale
- Name real systems that depend on well-chosen structures
Prerequisites
A phone book with one million names can be searched by hand in about twenty page flips, if it is sorted. If it is not sorted, you may read every page. The data and the method matter more than how fast your hands are.
Problems vs algorithms
A problem is a precise question: “given an array of integers, return its largest value”. An algorithm is a finite, step-by-step procedure that solves it. One problem has many algorithms, and an algorithm is only meaningful relative to the problem it claims to solve.
Searching a sorted array is one problem with at least three algorithms: linear search, binary search and interpolation search. They return the same answers and differ in cost.
Data vs information
Data is raw, uninterpreted values: 72, "Ana", true. Information is data in context: “Ana's heart rate is 72 beats per minute”. Programs store data and derive information from it.
How data is organised decides which questions are cheap. The same 72 stored in a sorted array answers “is anyone at 72?” quickly; stored in an unsorted list it needs a scan.
Data structures vs abstract data types
An abstract data type (ADT) describes behaviour: what operations exist and what they promise, such as a Stack with push, pop and peek. A data structure is a concrete way to store the data so those operations work: a stack can be an array with a size counter or a linked list with a head.
Java mirrors this split: List is an ADT (an interface), while ArrayList and LinkedList are data structures that implement it. Code that depends on the interface can switch structures without changing.
| ADT (what) | Possible structures (how) |
|---|---|
| List | array, linked list |
| Stack | array, linked list |
| Queue | circular array, linked list |
| Map | hash table, balanced tree |
| Priority queue | binary heap, balanced tree |
1List<Integer> items = new ArrayList<>(); // ADT on the left, structure on the right2items.add(7);3items.get(0);4// swap the structure, keep every line that uses `items`5items = new LinkedList<>();
Input-output contracts
A contract states what the caller must supply (preconditions) and what the code guarantees in return (postconditions). “Given a sorted array and a target, return its index or -1” is a contract. Binary search is correct for this contract and wrong for unsorted input, so the sortedness belongs in the contract, not in a hope.
Write the contract before the code: input types and ranges, what counts as valid, what to return for “not found” or empty input, and whether the input may be modified.
1/**2* Returns the index of target in arr, or -1 if absent.3* Precondition: arr is sorted ascending. Not modified.4* Postcondition: result == -1 or arr[result] == target.5*/6static int indexOf(int[] arr, int target) { ... }
Correctness vs efficiency
Correctness asks “does it give the right answer for every valid input?”. Efficiency asks “how much time and memory does it use as the input grows?”. They are independent: a brute-force solution can be perfectly correct and far too slow.
Compare the two solutions to the same two-sum problem. Both are correct. The first checks every pair; the second remembers what it has seen. Run both on the same input and compare the number of operations.
Brute force: check every pair
Interactive · edit the inputint[] arr · length 6
Try every pair (i, j) with i < j. There are n(n-1)/2 = 15 of them in the worst case.
1public static int[] twoSum(int[] nums, int target) {2for (int i = 0; i < nums.length; i++) {3for (int j = i + 1; j < nums.length; j++) {4if (nums[i] + nums[j] == target) {5return new int[] { i, j };6}7}8}9return new int[] { -1, -1 };10}
Live state
target
13
n
6
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | target=13 n=6 | Try every pair (i, j) with i < j. There are n(n-1)/2 = 15 of them in the worst case. |
| 2 | L2 | target=13 i=0 | Fix i = 0 (value 7) and pair it with every later element. |
| 3 | L4 | target=13 i=0 j=1 sum=18 | Not the target; try the next j. |
| 4 | L4 | target=13 i=0 j=2 sum=9 | Not the target; try the next j. |
| 5 | L4 | target=13 i=0 j=3 sum=22 | Not the target; try the next j. |
| 6 | L4 | target=13 i=0 j=4 sum=11 | Not the target; try the next j. |
| 7 | L4 | target=13 i=0 j=5 sum=16 | Not the target; try the next j. |
| 8 | L2 | target=13 i=1 | Fix i = 1 (value 11) and pair it with every later element. |
| 9 | L4 | target=13 i=1 j=2 sum=13 | This pair adds up to the target. |
| 10 | L5 | return=[1, 2] | return [1, 2] after 6 pair checks. Simple and obviously correct, but O(n²) in the worst case. |
Optimised: remember what you have seen
Interactive · edit the inputint[] arr · length 6
seen (value:index)
A hash map answers “have I seen this value?” in O(1) on average, so one pass is enough.
1public static int[] twoSum(int[] nums, int target) {2Map<Integer, Integer> seen = new HashMap<>(); // value -> index3for (int i = 0; i < nums.length; i++) {4int need = target - nums[i];5if (seen.containsKey(need)) {6return new int[] { seen.get(need), i };7}8seen.put(nums[i], i);9}10return new int[] { -1, -1 };11}
Live state
target
13
Execution trace (dry run table)Show 7 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | target=13 | A hash map answers “have I seen this value?” in O(1) on average, so one pass is enough. |
| 2 | L5 | i=0 nums[i]=7 need=6 | 13 - 7 = 6, which has not appeared yet. |
| 3 | L8 | i=0 size=1 | Store 7 → 0 for future lookups. |
| 4 | L5 | i=1 nums[i]=11 need=2 | 13 - 11 = 2, which has not appeared yet. |
| 5 | L8 | i=1 size=2 | Store 11 → 1 for future lookups. |
| 6 | L5 | i=2 nums[i]=2 need=11 | 11 is in the map: it was stored at index 1, so that value plus 2 makes 13. |
| 7 | L6 | return=[1, 2] | return [1, 2] after 3 lookups: O(n) time and O(n) extra memory. Brute force needed O(1) memory but O(n²) time. |
Scalability
Scalability is how cost grows as input grows. An algorithm that takes 1 ms for 1,000 items might take 1 ms × 1,000 = 1 s for a million items if it is linear, 11 days if it is quadratic, and longer than the age of the universe if it is exponential.
Move the slider below to see how the growth classes diverge. Small inputs hide the problem; large ones expose it.
How fast do the growth classes explode?
Interactive · drag the slidersO(1)
array index, hash lookup
1 ops
instant
O(log n)
binary search
7 ops
instant
O(√n)
jump search, trial division
10 ops
instant
O(n)
one pass
100 ops
instant
O(n log n)
merge sort
664 ops
instant
O(n²)
nested loops, bubble sort
10,000 ops
10 µs
O(n³)
triple loop, Floyd-Warshall
10^6.0 ops
1.0 ms
O(2ⁿ)
all subsets
10^30.1 ops
10^14 years
O(n!)
all permutations
10^158 ops
10^141 years
Bars use a log scale (the bar shows the exponent of 10). Time assumes 10⁹ simple operations per second, a common rule of thumb for competitive and interview limits. Slide n up and watch O(n²) cross one second near n ≈ 30,000 and O(2ⁿ) become impossible before n reaches 60.
Real-world use cases
The structures in this course run underneath everyday systems. Choosing the right one is usually worth more than tuning code.
| System | Structure or algorithm | Why it fits |
|---|---|---|
| Database index | B-tree | Few disk reads per lookup on sorted, large data |
| Autocomplete | Trie | Walk one character at a time along a shared prefix |
| Route planning | Graph + Dijkstra with a heap | Shortest path over weighted roads |
| Undo/redo | Two stacks | Last action undone first |
| Task scheduler | Priority queue | Always run the most urgent task next |
| Cache eviction (LRU) | Hash map + doubly linked list | O(1) lookup and O(1) reordering |
| Spell checker | Hash set | Average O(1) membership test |
Practice ladder
Now try it yourself
Easy
- Write the input-output contract for finding the maximum of an array
- Name an ADT and two structures that can implement it
Medium
- Give a correct but inefficient algorithm for the two-sum problem and state its cost
- List three real systems and the structure each one relies on
Advanced
- Show an example where two different algorithms satisfy the same contract but one violates a performance requirement
Key takeaways
- A problem has many algorithms; the contract says what any solution must do.
- An ADT is behaviour, a data structure is how it is stored.
- Correctness and efficiency are separate: check both, in that order.