Store Blog
Study & Reference Archive — Black / White Edition

I Memorised Hundreds of DSA Solutions. One Question Undid All of Them.

The question was Two Sum. I solved it in about ninety seconds. Not because I understood it. Because I had watched the same solution three times that week and my fingers knew where the HashMap went before my brain did. I remember feeling quietly pleased with myself. This was working. Two hundred problems in, and the answers were coming faster.

Then I opened the next one: find the length of the longest substring without repeating characters.

I sat there for forty minutes.

It wasn't a harder problem. It used the same data structure I'd just used. But nobody had shown me this one, and without a solution to recall, I had nothing. No approach, no starting point, not even a way to describe what I was looking for. I could recite an answer. I couldn't produce one.

That was the day I understood what I'd actually been doing for two months. I hadn't been learning to solve problems. I'd been building a lookup table in my head — and a lookup table only works when the question matches a key.

What memorisation actually stores

When you memorise a solution, you store the output of someone else's thinking. What you don't store is the part that matters: the sequence of decisions that produced it.

Watch a tutorial and you see a finished, edited artefact. The instructor says "so we'll use a HashMap here" and moves on. What you never see is the fifteen minutes where they tried something worse, noticed it was slow, worked out why it was slow, and only then reached for the HashMap. That missing middle is the entire skill.

So when a new problem arrives, you have a shelf full of answers and no idea which one to reach for — because reaching for the right one was never the thing you practised.

Experienced engineers are not carrying a bigger shelf. Plenty of them see an unfamiliar problem and have no idea what to do for the first few minutes either. The difference is that they have a reliable procedure for those first few minutes, and you don't. That procedure is learnable. Below is mine, run end to end on the exact problem that broke me.

First, what a data structure actually is

A quick correction before the process, because most explanations of this are too vague to be useful.

You'll often hear that a data structure is "a way to organise data." True, but it doesn't tell you anything you can act on. Here's the version that does:

A data structure is a set of trade-offs about which operations are cheap and which are expensive.

An array gives you instant access by index but slow insertion in the middle. A linked list flips that. A hash map gives you near-instant lookup by key but throws away ordering. A heap gives you the smallest element instantly and doesn't care about anything else.

None of them is better. Each one buys speed in one place by paying for it somewhere else. Once you see them this way, choosing a data structure stops being trivia recall and becomes a question you can answer: which operation is my solution doing over and over, and which structure makes that one cheap?

Hold on to that. It's the hinge for the whole process.

The process, on one real problem

The problem: given a string, return the length of the longest substring that contains no repeated characters. For "abcabcbb", the answer is 3 ("abc").

1. Restate it before you touch the keyboard

Not "read it carefully" — everyone says that and nobody knows what it means. Here's a concrete test: can you restate the problem in your own words without looking at it?

If you can't, you don't understand it yet, and any code you write is a guess.

While restating, pin down the boundaries:

I got this wrong the first time. I read "substring" and solved it as though characters could be skipped. Five minutes of reading would have saved me twenty-five of confusion.

2. Name the shape, not the trick

This is the step that separates people who improve from people who plateau.

Beginners ask "which algorithm is this?" — which only works if you've seen it before. The better question is "what shape is this?", because shape is visible even on a problem you've never encountered.

Read the restated problem and pull out its structural features:

Contiguous + optimise a length + constraint. That combination has a name: it's a sliding window. I didn't need to have seen this problem before to get there. I needed to notice those three features.

A sliding window moving across the string abcabcbb Four frames. The window first grows from one character to three, then its left edge jumps forward each time a repeated character enters on the right, so the window never contains a duplicate. 0123 4567 a bcabcbb abc abcbb a bca bcbb ab cab cbb end = 0grow end = 2grow end = 3'a' repeats end = 4'b' repeats len 1 len 3 len 3 len 3
The window grows on the right, and its left edge jumps forward only when a repeat forces it to.

A rough map from shape to family:

What the problem looks likeWhere to look first
Sorted input, searchingBinary search
Contiguous run, best length or sumSliding window
Pairs from a sorted arrayTwo pointers
"Have I seen this before?"Hash map or set
Repeated range sumsPrefix sum
Most recent thing mattersStack
Explore choices, undo, retryRecursion / backtracking

This is a starting direction, not an answer. It's enough.

3. Write the stupid version first

Resist optimising. Write the solution you'd get by doing exactly what the problem describes — check every substring, keep the longest valid one:

int longest = 0;
for (int i = 0; i < s.length(); i++) {
    Set<Character> seen = new HashSet<>();
    int j = i;
    while (j < s.length() && !seen.contains(s.charAt(j))) {
        seen.add(s.charAt(j));
        j++;
    }
    longest = Math.max(longest, j - i);
}
return longest;

That's O(n²). It's slow. It is also correct, and correct is the only thing you can optimise. You cannot speed up code that produces the wrong answer — you can only produce wrong answers faster.

There's a second reason this step matters, and it's the one people miss. The brute force is where the optimisation comes from. You can't see the waste until you've written the thing that wastes.

4. Find the waste, then remove it

Now look at what the naive version repeats. Restart at i = 1 and you re-scan almost the entire string you just scanned at i = 0. Every outer iteration throws away everything the previous one learned.

So the question becomes concrete: what did I learn last pass that I'm discarding?

The answer: I already know where each character last appeared. If I kept that, I wouldn't have to restart from scratch — I could jump the left edge of my window straight past the duplicate.

Which operation do I need to be cheap? "Where did I last see this character?" — a lookup by key. Hash map. This is the trade-off framing from earlier doing actual work, rather than me remembering that Two Sum used a HashMap.

Map<Character, Integer> lastSeen = new HashMap<>();
int longest = 0, start = 0;

for (int end = 0; end < s.length(); end++) {
    char c = s.charAt(end);
    if (lastSeen.containsKey(c) && lastSeen.get(c) >= start) {
        start = lastSeen.get(c) + 1;
    }
    lastSeen.put(c, end);
    longest = Math.max(longest, end - start + 1);
}
return longest;

One pass. O(n).

Notice what happened: the optimisation wasn't a trick I recalled. It fell out of asking what the slow version was throwing away. That question works on problems you've never seen.

5. State the complexity out loud

Time: O(n) — each index is visited once by end, and start only ever moves forward. Space: O(k), where k is the size of the character set.

The habit that matters here isn't reciting Big-O notation. It's asking: what happens when the input gets a thousand times bigger? The O(n²) version handles a 10,000-character string fine and dies on ten million. The O(n) version doesn't notice the difference. That gap is the whole reason complexity analysis exists.

Growth of O(n) against O(n squared) As input size grows, the work done by the linear solution rises gently while the quadratic solution curves sharply upward. The shaded gap between them widens continuously. O(n²) — brute force O(n) — sliding window input size n → work done
Both solutions look acceptable on a short string. The shaded gap is what the second version buys you as the input grows.

6. Dry run it on paper — and pick a hostile input

Do not run "abcabcbb". It works, it'll pass, and it will teach you nothing.

Pick the input designed to break you. For this problem it's "abba":

endcharlast seen atstartwindowlongest
0a0a1
1b0ab2
2b1 (≥ start) → start = 22b2
3a0 (< start) → leave alone2ba2

Answer: 2. Correct.

Now look at row 3, and at the lastSeen.get(c) >= start condition in the code. Drop that >= start guard and start jumps backwards to 1 at the final step, producing 3. A wrong answer, from code that looks completely reasonable and passes the sample input.

That single comparison is the actual difficulty of this problem. No tutorial ever flagged it to me. Four rows of pen and paper did.

7. Attack your own edge cases

Sample inputs are written to be friendly. Hidden test cases are not. Before submitting, run through:

This list takes ninety seconds and catches most of what separates "passed 34/40 test cases" from "accepted."

No, there is no pattern that solves everything

I want to be direct about this, because a lot of content online implies otherwise.

There is no master pattern. Interview questions are built by composing concepts — a graph problem with a dynamic-programming layer, a binary search over an answer space instead of an array. Any list of patterns you memorise will eventually meet a problem that sits between two of them.

That isn't a gap in your preparation. It's the permanent condition of the work.

What changes with experience isn't that unfamiliar problems stop appearing. It's that you stop treating an unfamiliar problem as evidence that you're not good enough, and start treating it as ordinary — something to be worked through with a procedure rather than recognised on sight.

What actually transfers

I still don't remember the code for most problems I've solved. I'd have to derive the sliding-window version above again if you asked me cold.

What I do have is a reliable sequence for the first ten minutes, which used to be the part where I panicked:

Restate it → name the shape → write the slow version → find what it wastes → check the complexity → dry run something hostile → break your own edge cases.

That's it. Seven steps, and none of them require you to have seen the problem before.

If you're two months in and quietly worried that you're not getting it — you might just be doing what I was doing. Collecting answers instead of practising the thing that produces them.

Stop memorising solutions. Start recording your reasoning.


Working through DSA? The archive has free PDF notes on data structures, algorithms, and computer science fundamentals — no account needed.

← Back to the blog