Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window
EasyLC 28

Find the Index of the First Occurrence in a String (LeetCode 28)

Target Frequency:GoogleMetaMicrosoft

You get two strings, a text haystack and a word needle. Look for the places where needle appears in haystack as a block of consecutive characters, and return the index in haystack where the leftmost of them begins.

If needle appears nowhere in haystack, return -1.

Worked Examples

Example 1
Input:haystack = "sadbutsad", needle = "sad"
Output:0
s0a1d2b3u4t5s6a7d8first sadsecond sad
Explanation: The scan stops at the first full match, which starts at index 0; the second copy of "sad", at index 6, is never reached.
Example 2
Input:haystack = "leetcode", needle = "leeto"
Output:-1
l0e1e2t3c4o5d6e7c != o
Explanation: "leet" matches at index 0, but the next letter is c, not o, and "leeto" appears nowhere else, so the answer is -1.

⚖️Formal Constraints & Bounds

  • 1 <= haystack.length, needle.length <= 104

  • haystack and needle consist of only lowercase English characters.

Deep-Dive & Conceptual Insights

Core Insight

After a mismatch, the characters just matched are a prefix of needle you already know, so the next place a match could start is set by needle alone: lps records, for every prefix, its longest border, and the search falls back to it instead of re-reading haystack.

Real-World Scenario & Production Applications

Searching a log stream for an error signature, a packet stream for an attack signature, or a DNA read for a marker sequence: the text arrives once and is long, so a search that backs up and re-reads it after every near-miss wastes work, or can't back up at all on a stream. KMP reads each character of the text exactly once.

Step-by-Step Execution Trace Table

The debugger's first preset, the trap case haystack = "abababc", needle = "ababc" (lps = [0, 0, 1, 2, 0]):

ichk beforeWhat the code doesk after
0a0ch == needle[0]: extend1
1b1ch == needle[1]: extend2
2a2ch == needle[2]: extend3
3b3ch == needle[3]: extend4
4a4a != needle[4] (c): k = lps[3] = 2, the border "ab" is kept; then a == needle[2]: extend3
5b3ch == needle[3]: extend4
6c4ch == needle[4]: extend, k == m5
return 6 - 5 + 1 = 2. Resetting k to 0 at i = 4 would have lost the match and returned -1.
Code Shape
Archetype Code Shape
Python
1After a mismatch, the letters you just matched are a prefix of `needle`, so you already know them: the next possible match is set by `needle` alone, not by re-reading `haystack`.
2Keep this true: after reading `haystack[i]`, `k` is the length of the longest prefix of `needle` that ends at `i`. `lps[j]` is the longest proper prefix of `needle[:j + 1]` that is also its suffix.
3The shape: two loops of the same form, first one over `needle` from index 1 that fills `lps`, then one over `haystack`. Inside each, shrink `k` while the next letter doesn't fit, then grow it on a match; the second loop stops as soon as all of `needle` is matched.
4The trap: on a mismatch set `k = lps[k - 1]`, never `k = 0`. On `"abababc"` with `needle = "ababc"` the reset loses the border `"ab"` and the only match, at index `2`.

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

The simple search tries every start in haystack and compares needle letter by letter, so after a near-miss it moves one step right and re-reads text it has already seen. That is up to N · M comparisons. KMP never re-reads: when a mismatch comes after k matched letters, those k letters are needle[:k], which you already know. The longest proper prefix of needle[:k] that is also its suffix, its longest border, is the most you can keep, because the next possible match must start at a suffix of what was read that is also a prefix of needle. lps[k - 1] stores that length, computed once from needle alone, so the search sets k = lps[k - 1] and tries the same text letter again.

🎼 The Analogy: Joining a Chant Halfway

You are listening for a chant, "ba-ba-ba-da", in a crowd. You hear "ba-ba-ba-ba" and the last syllable is wrong. You don't rewind the crowd and start listening from scratch: the last "ba-ba" you heard is already the start of the chant, so you carry on as if you had heard two syllables. Knowing, for every point in the chant, how much of it you may keep after a wrong syllable is exactly the lps table.

🔑 Why It Works

k is how much of needle ends at i. A mismatch shrinks k to the next shorter border, lps[k - 1], again and again until the letter fits or k is 0; a match grows it by one. Each letter of haystack raises k at most once and every fall back lowers it, so all the fall backs together cost at most N: the search is linear. The trap is resetting k = 0 on a mismatch: on "abababc" with needle = "ababc" it throws away the border "ab" at i = 4, which is where the only match begins.

  • Resetting on a mismatch (the trap): k = 0 throws away the border of what was just matched. On haystack = "abababc", needle = "ababc" the only match starts inside the failed attempt at index 0, so the reset returns -1 instead of 2; fall back with k = lps[k - 1].

  • if instead of while: one letter may need several fall backs. On haystack = "aabaa", needle = "aaa" a single if leaves k = 1 at the b and reports a match at index 2 that isn't there; the answer is -1.

  • lps[k] instead of lps[k - 1]: after k matched letters, the border you need is that of needle[:k], stored at lps[k - 1]; lps[k] belongs to a prefix one letter longer.

  • Returning the end: the match ends at i, so it starts at i - m + 1.

  • Trying every start: correct, but it re-reads the text after every near-miss: about 2.5 * 107 letter comparisons on "a" * 104 with needle = "a" * 4999 + "b".

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

Find needle in haystack "as a block of consecutive characters" and return "the leftmost of them": an exact pattern search in a text that can be long. Trying every start re-reads the text after every near-miss; KMP reads it once.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

After reading haystack[i], k is the length of the longest prefix of needle that ends at i. On a mismatch k = lps[k - 1] until ch == needle[k] or k == 0; on a match k += 1; at k == m the answer is i - m + 1.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • The constraints promise 1 <= needle.length, so lps = [0] * m and needle[k] are always safe here; a version that allows an empty needle must return 0 before the scan.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd use KMP. The naive search tries every start and re-reads the text after each near-miss, which is up to N times M comparisons. KMP never re-reads. First I build a table from the needle alone: for each prefix, the length of its longest proper prefix that is also a suffix. Then I scan the text once, keeping k, how much of the needle ends at the current letter. On a match I extend k; on a mismatch I don't go back in the text, I fall back to the table value for the k letters I just matched, because that border is the longest part of what I read that can still start a match, and I repeat while the letter doesn't fit. The trap is resetting k to zero, which loses matches that start inside a failed attempt. Each letter raises k at most once, so the scan is linear: O(N plus M) time and O(M) space.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N + M)

Look at the second loop: for i, ch in enumerate(haystack) runs N times, and each round does k += 1 at most once. The inner while looks quadratic, but every k = lps[k - 1] makes k strictly smaller, because lps[k - 1] < k, and k never goes below 0. k rises at most N times in all, so it can fall at most N times in all: the whole loop is O(N). The first loop is the same argument on needle against itself, O(M). Total: O(N + M).

SPACE COMPLEXITY

O(M)

lps holds M integers; k, i and ch are single values. Space: O(M), and the answer is one integer.

Formal Recurrence Relation

T(N, M) = O(M) to build lps + O(N) to scan = O(N + M)

Derivation Progression

Build lps

O(M)

for i in range(1, m): k += 1 at most once per round, and each k = lps[k - 1] lowers k, so the fall backs add up to at most M.

Scan haystack

O(N)

for i, ch in enumerate(haystack): k rises at most once per letter, so it falls at most N times in all.

Total

O(N + M)

Two linear passes; no letter of haystack is read twice.

Variable Definitions

NNN

Length of haystack

MMM

Length of needle, m in the code

Memory Architecture & Bounds

🟣 Call Stack

O(1) Iterative, no recursion

🔵 Auxiliary Heap

O(M): lps

🟢 Output Space

O(1): one integer

Boundary Best / Worst Cases

Best Case

O(M)O(M)O(M) plus a short scan: a match at index 0 returns after M letters of haystack

Average Case

O(N+M)O(N + M)O(N+M)

Worst Case

O(N+M)O(N + M)O(N+M): no match, or a match at the very end

Pointer Invariant Transition Progression

Pointer Invariant Transition Progression
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "as a block of consecutive characters", "the leftmost of them". Find a word in a long text, the text read once: KMP, a border table of the word and a scan that never backs up.

CONSTRAINTS & BOUNDS

N,M≤104N, M \le 10^4N,M≤104. Trying every start is O(N⋅M)O(N \cdot M)O(N⋅M), about 2.5⋅1072.5 \cdot 10^72.5⋅107 letter comparisons when needle is half of haystack; KMP is O(N+M)O(N + M)O(N+M), and a rolling hash (Rabin-Karp) is O(N+M)O(N + M)O(N+M) expected.

FAANG PRODUCTION TRAPS & EDGE CASES

For many patterns at once, the same border idea becomes the Aho-Corasick automaton; for one pattern, str.find in CPython is already a linear-time search in C.

Core Algorithmic State Invariants

  1. A Surviving Match Starts at a Border

Any match still possible after a mismatch starts inside the k letters just read, so its beginning is a suffix of needle[:k] that is also a prefix of needle: a border. lps[k - 1] is the longest one, and each further fall back moves to the next shorter one, so no possible start is skipped.

  1. The Pattern Describes Itself

lps is built from needle alone, with the same while k > 0 and needle[i] != needle[k]: k = lps[k - 1] loop the search uses: building it is searching needle in itself.

Theory Context•Two Pointers & Sliding Window
EasyLC 28

Find the Index of the First Occurrence in a String (LeetCode 28)

Target Frequency:GoogleMetaMicrosoft

You get two strings, a text haystack and a word needle. Look for the places where needle appears in haystack as a block of consecutive characters, and return the index in haystack where the leftmost of them begins.

If needle appears nowhere in haystack, return -1.

Worked Examples

Example 1
Input:haystack = "sadbutsad", needle = "sad"
Output:0
s0a1d2b3u4t5s6a7d8first sadsecond sad
Explanation: The scan stops at the first full match, which starts at index 0; the second copy of "sad", at index 6, is never reached.
Example 2
Input:haystack = "leetcode", needle = "leeto"
Output:-1
l0e1e2t3c4o5d6e7c != o
Explanation: "leet" matches at index 0, but the next letter is c, not o, and "leeto" appears nowhere else, so the answer is -1.

⚖️Formal Constraints & Bounds

  • 1 <= haystack.length, needle.length <= 104

  • haystack and needle consist of only lowercase English characters.

Deep-Dive & Conceptual Insights

Core Insight

After a mismatch, the characters just matched are a prefix of needle you already know, so the next place a match could start is set by needle alone: lps records, for every prefix, its longest border, and the search falls back to it instead of re-reading haystack.

Real-World Scenario & Production Applications

Searching a log stream for an error signature, a packet stream for an attack signature, or a DNA read for a marker sequence: the text arrives once and is long, so a search that backs up and re-reads it after every near-miss wastes work, or can't back up at all on a stream. KMP reads each character of the text exactly once.

Step-by-Step Execution Trace Table

The debugger's first preset, the trap case haystack = "abababc", needle = "ababc" (lps = [0, 0, 1, 2, 0]):

ichk beforeWhat the code doesk after
0a0ch == needle[0]: extend1
1b1ch == needle[1]: extend2
2a2ch == needle[2]: extend3
3b3ch == needle[3]: extend4
4a4a != needle[4] (c): k = lps[3] = 2, the border "ab" is kept; then a == needle[2]: extend3
5b3ch == needle[3]: extend4
6c4ch == needle[4]: extend, k == m5
return 6 - 5 + 1 = 2. Resetting k to 0 at i = 4 would have lost the match and returned -1.
Code Shape
Archetype Code Shape
Python
1After a mismatch, the letters you just matched are a prefix of `needle`, so you already know them: the next possible match is set by `needle` alone, not by re-reading `haystack`.
2Keep this true: after reading `haystack[i]`, `k` is the length of the longest prefix of `needle` that ends at `i`. `lps[j]` is the longest proper prefix of `needle[:j + 1]` that is also its suffix.
3The shape: two loops of the same form, first one over `needle` from index 1 that fills `lps`, then one over `haystack`. Inside each, shrink `k` while the next letter doesn't fit, then grow it on a match; the second loop stops as soon as all of `needle` is matched.
4The trap: on a mismatch set `k = lps[k - 1]`, never `k = 0`. On `"abababc"` with `needle = "ababc"` the reset loses the border `"ab"` and the only match, at index `2`.

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

The simple search tries every start in haystack and compares needle letter by letter, so after a near-miss it moves one step right and re-reads text it has already seen. That is up to N · M comparisons. KMP never re-reads: when a mismatch comes after k matched letters, those k letters are needle[:k], which you already know. The longest proper prefix of needle[:k] that is also its suffix, its longest border, is the most you can keep, because the next possible match must start at a suffix of what was read that is also a prefix of needle. lps[k - 1] stores that length, computed once from needle alone, so the search sets k = lps[k - 1] and tries the same text letter again.

🎼 The Analogy: Joining a Chant Halfway

You are listening for a chant, "ba-ba-ba-da", in a crowd. You hear "ba-ba-ba-ba" and the last syllable is wrong. You don't rewind the crowd and start listening from scratch: the last "ba-ba" you heard is already the start of the chant, so you carry on as if you had heard two syllables. Knowing, for every point in the chant, how much of it you may keep after a wrong syllable is exactly the lps table.

🔑 Why It Works

k is how much of needle ends at i. A mismatch shrinks k to the next shorter border, lps[k - 1], again and again until the letter fits or k is 0; a match grows it by one. Each letter of haystack raises k at most once and every fall back lowers it, so all the fall backs together cost at most N: the search is linear. The trap is resetting k = 0 on a mismatch: on "abababc" with needle = "ababc" it throws away the border "ab" at i = 4, which is where the only match begins.

  • Resetting on a mismatch (the trap): k = 0 throws away the border of what was just matched. On haystack = "abababc", needle = "ababc" the only match starts inside the failed attempt at index 0, so the reset returns -1 instead of 2; fall back with k = lps[k - 1].

  • if instead of while: one letter may need several fall backs. On haystack = "aabaa", needle = "aaa" a single if leaves k = 1 at the b and reports a match at index 2 that isn't there; the answer is -1.

  • lps[k] instead of lps[k - 1]: after k matched letters, the border you need is that of needle[:k], stored at lps[k - 1]; lps[k] belongs to a prefix one letter longer.

  • Returning the end: the match ends at i, so it starts at i - m + 1.

  • Trying every start: correct, but it re-reads the text after every near-miss: about 2.5 * 107 letter comparisons on "a" * 104 with needle = "a" * 4999 + "b".

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

Find needle in haystack "as a block of consecutive characters" and return "the leftmost of them": an exact pattern search in a text that can be long. Trying every start re-reads the text after every near-miss; KMP reads it once.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

After reading haystack[i], k is the length of the longest prefix of needle that ends at i. On a mismatch k = lps[k - 1] until ch == needle[k] or k == 0; on a match k += 1; at k == m the answer is i - m + 1.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • The constraints promise 1 <= needle.length, so lps = [0] * m and needle[k] are always safe here; a version that allows an empty needle must return 0 before the scan.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd use KMP. The naive search tries every start and re-reads the text after each near-miss, which is up to N times M comparisons. KMP never re-reads. First I build a table from the needle alone: for each prefix, the length of its longest proper prefix that is also a suffix. Then I scan the text once, keeping k, how much of the needle ends at the current letter. On a match I extend k; on a mismatch I don't go back in the text, I fall back to the table value for the k letters I just matched, because that border is the longest part of what I read that can still start a match, and I repeat while the letter doesn't fit. The trap is resetting k to zero, which loses matches that start inside a failed attempt. Each letter raises k at most once, so the scan is linear: O(N plus M) time and O(M) space.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N + M)

Look at the second loop: for i, ch in enumerate(haystack) runs N times, and each round does k += 1 at most once. The inner while looks quadratic, but every k = lps[k - 1] makes k strictly smaller, because lps[k - 1] < k, and k never goes below 0. k rises at most N times in all, so it can fall at most N times in all: the whole loop is O(N). The first loop is the same argument on needle against itself, O(M). Total: O(N + M).

SPACE COMPLEXITY

O(M)

lps holds M integers; k, i and ch are single values. Space: O(M), and the answer is one integer.

Formal Recurrence Relation

T(N, M) = O(M) to build lps + O(N) to scan = O(N + M)

Derivation Progression

Build lps

O(M)

for i in range(1, m): k += 1 at most once per round, and each k = lps[k - 1] lowers k, so the fall backs add up to at most M.

Scan haystack

O(N)

for i, ch in enumerate(haystack): k rises at most once per letter, so it falls at most N times in all.

Total

O(N + M)

Two linear passes; no letter of haystack is read twice.

Variable Definitions

NNN

Length of haystack

MMM

Length of needle, m in the code

Memory Architecture & Bounds

🟣 Call Stack

O(1) Iterative, no recursion

🔵 Auxiliary Heap

O(M): lps

🟢 Output Space

O(1): one integer

Boundary Best / Worst Cases

Best Case

O(M)O(M)O(M) plus a short scan: a match at index 0 returns after M letters of haystack

Average Case

O(N+M)O(N + M)O(N+M)

Worst Case

O(N+M)O(N + M)O(N+M): no match, or a match at the very end

Pointer Invariant Transition Progression

Pointer Invariant Transition Progression
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "as a block of consecutive characters", "the leftmost of them". Find a word in a long text, the text read once: KMP, a border table of the word and a scan that never backs up.

CONSTRAINTS & BOUNDS

N,M≤104N, M \le 10^4N,M≤104. Trying every start is O(N⋅M)O(N \cdot M)O(N⋅M), about 2.5⋅1072.5 \cdot 10^72.5⋅107 letter comparisons when needle is half of haystack; KMP is O(N+M)O(N + M)O(N+M), and a rolling hash (Rabin-Karp) is O(N+M)O(N + M)O(N+M) expected.

FAANG PRODUCTION TRAPS & EDGE CASES

For many patterns at once, the same border idea becomes the Aho-Corasick automaton; for one pattern, str.find in CPython is already a linear-time search in C.

Core Algorithmic State Invariants

  1. A Surviving Match Starts at a Border

Any match still possible after a mismatch starts inside the k letters just read, so its beginning is a suffix of needle[:k] that is also a prefix of needle: a border. lps[k - 1] is the longest one, and each further fall back moves to the next shorter one, so no possible start is skipped.

  1. The Pattern Describes Itself

lps is built from needle alone, with the same while k > 0 and needle[i] != needle[k]: k = lps[k - 1] loop the search uses: building it is searching needle in itself.

© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•