Putting It Together

Table of Contents

  1. What You Have Built
  2. Reading a Problem for Signals
  3. The Pattern Cheat Sheet
  4. Cost Cheat Sheet
  5. The Seven-Step Method
  6. A Worked Example
  7. Common Mistakes
  8. Beyond Interviews: Choosing at Work
  9. Recipe: index.ts for algolab
  10. Try It Yourself
  11. Where to Go Next

1. What You Have Built

Take a moment to look back. Over thirteen chapters you learned the vocabulary of a working programmer:

  • Everyday structures: arrays and strings, hash maps and sets, linked lists, stacks and queues.
  • Core techniques: recursion, searching, and sorting.
  • Structures for hierarchy and relationships: trees, heaps, and graphs.
  • Specialists: tries and union-find.
  • Ways of designing: greedy choices and dynamic programming.

You also wrote a library, algolab, with tests, that holds your own version of most of them. That matters more than it may seem. When someone says "a priority queue is O(log n) to push", you do not have to take their word for it, because you have written the sift-up loop and seen it.

This chapter does not add new theory. It does three things:

  1. It turns the series into a cheat sheet for choosing the right tool.
  2. It gives you a method for any unfamiliar problem, in an interview or on the job.
  3. It ties algolab together with one entry file.

2. Reading a Problem for Signals

Most problems tell you what they need, if you read carefully. The trick is to treat words in the problem as signals, the way a doctor treats symptoms. Here are the ones that appear most often:

Phrase in the problemThink of
"Have I seen this before?", "duplicates", "count how many of each"Hash set or hash map
"Find two items that add up to ..."Hash map of seen values
"Sorted array", "find in logarithmic time"Binary search
"Longest / shortest contiguous part"Sliding window
"Pairs from both ends", "palindrome", "sorted, find a pair"Two pointers
"Sum of a range", many range queriesPrefix sums
"Matching brackets", "undo", "most recent first"Stack
"In order of arrival", "level by level", "shortest steps"Queue, BFS
"Biggest / smallest K", "next task by priority"Heap
"Hierarchy", "folders", "parent and child"Tree, DFS
"Network", "map", "prerequisites", "connections"Graph
"Words that start with ..."Trie
"Are these in the same group?", groups mergeUnion-find
"All combinations / permutations / subsets"Backtracking (recursion)
"Fewest / most / how many ways", repeated subproblemsDynamic programming
"Largest set of non-overlapping ...", clear sort orderGreedy

No single phrase decides anything on its own. Use the table to generate candidates, then check them against the constraints.

The input size is a signal, too. It tells you how much work is allowed:

Input size nRoughly allowedTypical approach
up to 10 to 20O(2^n) or O(n!)Backtracking, try everything
up to 500O(n^3)Triple loops, 3D tables
up to 5,000O(n^2)Double loops, 2D DP
up to 100,000O(n log n)Sorting, heaps, binary search
up to 1,000,000 or moreO(n) or O(log n)Hash maps, two pointers, one pass

These are rough rules for a time limit of about a second. If an interviewer says "up to a million items", an O(n^2) answer is out before you write a line.

3. The Pattern Cheat Sheet

Here is the series on one page. For each structure or technique: what it is great at, the typical cost, and the chapter to revisit.

ToolGreat atTypical costChapter
ArrayIndex access, appending, scanningRead O(1), insert in the middle O(n)2
Two pointersSorted pairs, palindromes, in-place editsO(n)2
Sliding windowBest contiguous range with a ruleO(n)2
Prefix sumsRange sums after one passO(n) build, O(1) query2
Hash map / setLookup, counting, grouping, dedupeO(1) average3
Linked listCheap insert/remove at a known nodeO(1) there, O(n) to find4
StackUndo, matching, depth-first orderO(1) push/pop5
QueueFair order, BFS, bufferingO(1) enqueue/dequeue5
RecursionSelf-similar problems, trees, backtrackingDepends, watch stack depth6
Binary searchSorted data, or "smallest value that works"O(log n)7
SortingOrdering, grouping neighbors, greedy prepO(n log n)8
Binary search treeOrdered data with insertsO(log n) balanced, O(n) skewed9
Tree traversalHierarchies: DFS and BFSO(n)9
HeapTop K, scheduling, merge sorted listsO(log n) push/pop10
Graph + BFSShortest path with equal stepsO(V + E)11
Graph + DFSCycles, components, orderingO(V + E)11
DijkstraShortest path with weightsO((V + E) log V)11
TriePrefix search, autocompleteO(L) per word12
Union-findDynamic grouping, cycle detectionalmost O(1)12
GreedyProvable "best now is best overall"usually sort + one pass13
Dynamic programmingOptimize/count over overlapping subproblemsstates × choices13

A few moves recur often enough to memorize:

  • "I need to look things up fast": put them in a Map or Set. This single move turns many O(n^2) answers into O(n).
  • "I need the extreme item, repeatedly": a heap.
  • "I need things sorted, then a pass": sort first. Many problems become easy once neighbors are adjacent (intervals, duplicates, pairs).
  • "The answer is a number in a range, and I can check a guess": binary search on the answer.
  • "Explore all possibilities": backtracking, then prune or memoize to speed it up.
  • "Order of choices matters little, only counts": DP.

4. Cost Cheat Sheet

When you pick a structure, you pick its costs. Keep this table in your head, as you compared many of these costs in earlier chapters.

StructureAccess by indexSearchInsertDelete
ArrayO(1)O(n)O(n), O(1) at the endO(n), O(1) at the end
Sorted arrayO(1)O(log n)O(n)O(n)
Linked listO(n)O(n)O(1) at a known nodeO(1) at a known node
Hash map / setn/aO(1) avgO(1) avgO(1) avg
Stack / queuen/aO(n)O(1)O(1)
Balanced BSTn/aO(log n)O(log n)O(log n)
Binary heapn/aO(n)O(log n)O(log n) for the top
Trien/aO(L)O(L)O(L)

And the same for sorting:

AlgorithmBestAverageWorstExtra spaceStable
Insertion sortO(n)O(n^2)O(n^2)O(1)Yes
Merge sortO(n log n)O(n log n)O(n log n)O(n)Yes
Quick sortO(n log n)O(n log n)O(n^2)O(log n)No
Heap sortO(n log n)O(n log n)O(n log n)O(1)No
Array.prototype.sortO(n)O(n log n)O(n log n)variesYes

In practice you will use the built-in sort (with a comparator) almost every time. The point of learning the others is to recognize their ideas and costs, not to rewrite them at work.

5. The Seven-Step Method

When you meet a problem you do not recognize, panic is the real enemy. A fixed routine gives you something to do while your brain catches up. It also tells an interviewer how you think, which is what they came to learn. The seven steps:

  1. Clarify. Restate the problem in your own words. Ask about the input: how big? Can it be empty? Can it contain negatives or duplicates? Is it sorted? What should happen on bad input?
  2. Examples. Write two or three examples, including one tricky edge case (empty, single item, all the same). Work one by hand. Often you discover the algorithm while doing this.
  3. Brute force. State the simplest solution, even if slow, and its cost. This proves you understand the problem and gives you a fallback that is correct.
  4. Optimize. Ask what work is repeated or wasted. Check the signals from section 2. Does a hash map remove an inner loop? Does sorting help? Can you reuse the previous answer (a window, a prefix sum, a DP table)?
  5. Code. Write the optimized version, in small named steps. Say what you are doing as you go. Use clear variable names.
  6. Test. Run your examples through the code by hand (or really run them). Then try the edge cases: empty, one element, duplicates, largest value. Fix what you find without embarrassment.
  7. Complexity. State the time and the space cost, and say why. Mention trade-offs: "I could use O(1) space with sorting, but that costs O(n log n) time."

Two habits matter more than any single trick:

  • Think out loud. An interviewer cannot hear silence. Even a wrong idea, spoken and corrected, shows good habits.
  • Start with correct, then fast. A working brute force beats an unfinished clever solution. You can always improve a working version.

6. A Worked Example

Let us run the method on a real problem.

Given a string s, return the length of the longest substring that contains no repeated characters.

1. Clarify. A substring is contiguous (unlike a subsequence). "Characters" means any character, case-sensitive. An empty string returns 0. Assume s can be up to 100,000 characters, which rules out O(n^2) in a hurry.

2. Examples.

InputLongest unique substringAnswer
"abcabcbb""abc"3
"bbbbb""b"1
"pwwkew""wke"3
""(none)0
"abba""ab" or "ba"2

The "abba" case is the tricky one, and we will come back to it.

3. Brute force. For every start position, extend to the right until a repeat appears, remembering the best length:

function longestUniqueBrute(s: string): number {
  let best = 0;
  for (let i = 0; i < s.length; i++) {
    const seen = new Set<string>();
    for (let j = i; j < s.length; j++) {
      if (seen.has(s[j])) break;
      seen.add(s[j]);
      best = Math.max(best, j - i + 1);
    }
  }
  return best;
}

It is correct. The cost is O(n^2) time in the worst case (a string with no repeats, such as many different characters), and O(min(n, alphabet)) space. For 100,000 characters that is too slow, so we continue.

4. Optimize. What is wasted? After we find a repeat at position j, we throw everything away and restart from i + 1. But the substring from i + 1 up to j - 1 was already known to be unique. Signals from the cheat sheet: "longest contiguous part with a rule" means sliding window (chapter 2), and "have I seen this character" means a hash map (chapter 3).

The plan: keep a window [start, end] that is always free of repeats. Move end one step right each time. If s[end] was seen inside the window, move start just past its previous position. Store the last index of each character in a Map.

5. Code.

function longestUnique(s: string): number {
  const lastSeen = new Map<string, number>(); // char -> last index
  let start = 0;
  let best = 0;

  for (let end = 0; end < s.length; end++) {
    const ch = s[end];
    const prev = lastSeen.get(ch);
    if (prev !== undefined && prev >= start) {
      start = prev + 1; // jump past the earlier copy
    }
    lastSeen.set(ch, end);
    best = Math.max(best, end - start + 1);
  }
  return best;
}

6. Test. Run the examples through both versions:

for (const s of ["abcabcbb", "bbbbb", "pwwkew", "", "abba", "dvdf"]) {
  console.log(JSON.stringify(s), longestUniqueBrute(s), longestUnique(s));
}
"abcabcbb" 3 3
"bbbbb" 1 1
"pwwkew" 3 3
"" 0 0
"abba" 2 2
"dvdf" 3 3

Both agree. Having the brute-force version around is a real gift: it is a test oracle. Whenever you write a clever solution, compare it to the slow obvious one on many random inputs. Disagreements point right at your bug.

Now back to "abba". At end = 3 the second a finds prev = 0, but start is already 2 (moved by the second b). Without the check prev >= start, we would move start backward to 1 and count "bba", which has a repeat. That one comparison is the line most people forget. Testing the tricky example is what caught it.

7. Complexity. Each character is visited once by end, and start only moves forward, so time is O(n). The map holds at most one entry per distinct character, so space is O(min(n, alphabet size)). We turned O(n^2) into O(n) by spotting wasted work and adding a hash map. That is the typical shape of an interview answer.

7. Common Mistakes

After many problems, the same slips appear. Check your code for these before you say "done":

  • Off-by-one in loops and ranges. < versus <=, length versus length - 1, a half-open range [lo, hi) versus a closed one (chapter 7).
  • Forgetting the empty input. Empty array, empty string, 0 items, null head of a list.
  • Mutating the input. sort() changes the original array. Copy it first ([...arr].sort(...)) unless the problem allows it.
  • Default sort() on numbers. It compares as strings, so [10, 9, 1].sort() gives [1, 10, 9]. Always pass (a, b) => a - b (chapter 8).
  • shift() in a loop. It is O(n) on a plain array. For a queue, use an index pointer or a real queue (chapter 5).
  • Hash map key surprises. Map compares objects by identity; use a string key such as `${x},${y}` for coordinates.
  • Missing base case or visited set. The first gives a stack overflow, the second gives an infinite loop in graphs (chapters 6, 11).
  • Using greedy without proof. Test it against a small counter-example before trusting it (chapter 13).
  • Integer limits. Numbers lose precision past 2^53 - 1. Use BigInt when a count can grow that large (see the Math series).
  • Not stating complexity. Always say it. It is half the answer.

8. Beyond Interviews: Choosing at Work

Interview problems are small and neat. Real software is large and messy, but the same thinking applies. Some honest guidance:

  • Measure first. Most code is not the bottleneck. A clear O(n^2) loop over 50 items is fine. Profile (in Node.js, --cpu-prof or the browser's Performance tab) before you rewrite anything.
  • Prefer the built-ins. Map, Set, Array.prototype.sort, and well-tested libraries beat your own versions for correctness and speed. Writing your own structure is for learning, or for when a library does not fit.
  • Think about the shape of the data. What is the biggest size it will reach in one year? A list of 20 items and a feed of 20 million rows are different problems.
  • Remember the database. A WHERE clause on an indexed column is a B-tree search, and a JOIN is a hash or merge algorithm. The ideas in this series explain why queries are fast or slow. That is the bridge to the SQL series.
  • Readability counts. A slightly slower version that your team can understand is often the better engineering choice. Write a short comment when you pick an unusual structure, and say why.

9. Recipe: index.ts for algolab

Time to tie your library together. Each module you wrote becomes a namespace in one entry file, so there are no name clashes between modules that happen to export similar names.

Create src/index.ts:

export * as arrays from "./arrays.ts";
export * as hashMap from "./hash-map.ts";
export * as linkedList from "./linked-list.ts";
export * as stack from "./stack.ts";
export * as queue from "./queue.ts";
export * as recursion from "./recursion.ts";
export * as searching from "./searching.ts";
export * as sorting from "./sorting.ts";
export * as bst from "./bst.ts";
export * as priorityQueue from "./priority-queue.ts";
export * as graph from "./graph.ts";
export * as trie from "./trie.ts";
export * as unionFind from "./union-find.ts";
export * as greedy from "./greedy.ts";
export * as dp from "./dp.ts";

The file names above follow the recipes in this series, one module per chapter. If you named any of yours differently, or put two structures in one file, change the path on that line to match. Run ls src to see what you have.

Now a small demo that uses the library the way a user would. Create demo.ts in the algolab folder:

import { trie, unionFind, dp, greedy } from "./src/index.ts";

// A tiny autocomplete.
const words = new trie.Trie();
for (const w of ["dog", "door", "dot", "cat"]) words.insert(w);
console.log(words.suggest("do"));

// How many separate networks?
console.log(unionFind.countComponents(5, [[0, 1], [1, 2], [3, 4]]));

// Fewest coins, and a case where greedy loses.
console.log(dp.coinChange([4, 3, 1], 6));
console.log(greedy.greedyCoins([4, 3, 1], 6));

// Longest common subsequence.
console.log(dp.lcs("AGGTAB", "GXTXAYB"));
npx tsx demo.ts
[ 'dog', 'door', 'dot' ]
2
2
[ 4, 1, 1 ]
4

Add lines for your other modules (a HashMap, a PriorityQueue, a Graph) and call them the same way, for example new graph.Graph(), once you check the class names you chose in those chapters.

Finally, run your entire test suite once to see the whole library at work:

npx tsx --test

A passing suite of tests that you wrote, for structures that you built, is a good thing to have. Commit it, and keep it as a reference you can come back to before an interview.

10. Try It Yourself

  1. Run the method. Pick a problem you have not seen: "Given an array of integers, return the length of the longest run of consecutive numbers (like 100, 4, 200, 1, 3, 2 gives 4) in O(n)." Go through all seven steps on paper before coding. Hint: a Set and a check for "is this the start of a run?" (Interview-style.)
  2. Brute force as an oracle. Write a random tester for longestUnique: generate 1,000 random strings of letters a to d (length 0 to 12) and assert that it equals longestUniqueBrute for each. Does it ever disagree? Remove the prev >= start check and run it again to see the test catch the bug.
  3. Pick the tool. For each, name the structure or technique and the cost, without coding: (a) "Show the 10 most-liked posts out of 5 million." (b) "Do these two words share any letters?" (c) "Fewest flights to get from city A to city B." (d) "Check whether a list of build tasks has a circular dependency." (e) "Merge k sorted log files into one."
  4. Mock interview. Take any chapter's exercise list, choose one problem at random, and solve it in 25 minutes talking out loud, as if to an interviewer. Record yourself if you can, and listen for long silences and skipped steps.
  5. Complete algolab. Make sure every module has at least one test and that npx tsx --test is green. Then add a README.md listing, for each module, what it is for and the cost of its main operations.
  6. Two-sum, three ways. Solve "find two numbers that add to a target" with (a) a double loop, (b) sorting and two pointers, and (c) a hash map. Compare the time and space of each, and write one sentence on when you would choose each.

11. Where to Go Next

You now have the foundation that technical interviews, and much of everyday engineering, are built on. A few ways to continue:

  • Practice, a little each day. The skill grows with repetition on problems you have never seen. Pick a set of problems grouped by pattern (arrays, graphs, DP) and do two or three a week. Quality beats volume: after each problem, write down the signal that led you to the technique.
  • Go deeper on databases. Everything about trees, hashing, and sorting shows up in how databases store and query data. The SQL series on this site builds on this directly: indexes are B-trees, joins are hash or merge algorithms, and ORDER BY is sorting.
  • Learn system design. The next step up in scale is combining these pieces: a cache is a hash map with an eviction rule, a job queue is a queue, a rate limiter is a sliding window, a leaderboard is a heap or sorted structure, a search service is a trie or an index, and a feed is a merge of sorted lists. Thinking in structures makes the larger designs easier to reason about.
  • Revisit the chapters. Pick the one you found hardest. It will feel easier the second time, and you now have the later chapters to give it context. The Math for Programmers series, especially its Big-O chapter, is a good companion for the analysis side.
  • Keep building. Use algolab in a small real project, such as a command-line autocomplete, a route finder, or a task scheduler. Writing something you actually want to use cements the ideas better than any exercise.

Thank you for working through the series. You started with "why do data structures matter", and now you can look at a problem, name the shape it has, choose a structure with its costs in mind, and explain your reasoning clearly. That is the real skill, and it stays with you long after the details fade.

To start again from the beginning, or to review a topic, return to Why Data Structures and Algorithms Matter.

Buy Me a Coffee

Found this tutorial helpful? Consider buying me a coffee to support more free content!