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 21

Merge Two Sorted Lists (LeetCode 21)

Target Frequency:AmazonGoogleMeta

Two singly linked lists, list1 and list2, are each sorted from the smallest value to the largest. Equal values are allowed.

Join them into one list that is also sorted. Reuse the nodes that already exist: do not create new nodes to hold the values.

Return the head of the joined list. If both lists are empty, the answer is an empty list.

Worked Examples

Example 1
Input:list1 = [1,2,4], list2 = [1,3,4]
Output:[1,1,2,3,4,4]
list1
124
list2
134
Explanation: At every step the smaller of the two front values goes next. The two front values 1 and 1 are equal, so either node may go first; the result is the same.
Example 2
Input:list1 = [], list2 = []
Output:[]
Explanation: Both lists are empty, so the merged list is empty.
Example 3
Input:list1 = [], list2 = [0]
Output:[0]
Explanation: One list is empty, so the answer is the other list as it is.

⚖️Formal Constraints & Bounds

  • The number of nodes in both lists is in the range [0, 50].

  • -100 <= Node.val <= 100

  • list1 and list2 are each sorted in non-decreasing order.

Deep-Dive & Conceptual Insights

Core Insight

Both lists are sorted, so the smaller of the two front nodes is the smallest unread node of both lists: attach it and move only its pointer. Each node is attached once, in sorted order. When one list is empty, the other list is already sorted and linked, so one assignment attaches all of it.

Real-World Scenario & Production Applications

The merge step of merge sort, combining two sorted log files into one in time order, and the merge join that a database uses on two inputs already sorted by the join key.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Take The Smaller Head

Keep one pointer per list. The smaller head is the smallest unread node of both lists, so attach it to tail and move only the pointer of the list it came from.

Mathematical Recurrence / Code Invariant
while list1 and list2:
    if list1.val <= list2.val:
        tail.next = list1
        list1 = list1.next
    else:
        tail.next = list2
        list2 = list2.next
    tail = tail.next

Step-by-Step Execution Trace Table

Steplist1.vallist2.valBranchNode attached to tailMerged list so far
1111 <= 1: take from list11 (list1)1
2212 <= 1 is False: take from list21 (list2)1, 1
3232 <= 3: take from list12 (list1)1, 1, 2
4434 <= 3 is False: take from list23 (list2)1, 1, 2, 3
5444 <= 4: take from list1; list1 is now empty4 (list1)1, 1, 2, 3, 4
6(empty)4Loop ends. tail.next = list2 attaches the rest4 (list2)1, 1, 2, 3, 4, 4
7return dummy.next1, 1, 2, 3, 4, 4
Code Shape
Archetype Code Shape
Python
1dummy = ListNode(0)
2tail = dummy
3while list1 and list2:
4 if list1.val <= list2.val:
5 tail.next = list1
6 list1 = list1.next
7 else:
8 tail.next = list2
9 list2 = list2.next
10 tail = tail.next
11tail.next = list1 if list1 else list2
12return dummy.next

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Two pointers here walk two different sequences at once, not one: each sorted list has its own pointer. Both lists are sorted, so the first unread node of each list is the smallest one that list still has. The smaller of the two is therefore the smallest unread node of both lists, and it is the next node of the answer.

🗂️ The Analogy: Two Sorted Piles of Index Cards

Imagine two piles of index cards, each sorted with the smallest card on top. To build one sorted pile, look at the top card of each pile, move the smaller one to the new pile, and look again. When one pile is empty, the other pile is already sorted, so you place the whole pile on the new one in a single move.

🔑 The Key Idea in the Code

The loop stops as soon as either list is empty, so one more line must attach the list that is not empty. Leave it out and every node after the last comparison is lost.

  • No attach after the loop: while list1 and list2 stops as soon as either list is empty, so tail.next = list1 if list1 else list2 must still attach the other list. Without it LeetCode's first example returns [1,1,2,3,4] instead of [1,1,2,3,4,4]: the last 4 of list2 is never attached.

  • Looping while either list has a node: while list1 or list2 reads list1.val on an empty list and raises an error. Loop while both have a node, then attach the rest.

  • Forgetting to move tail: without tail = tail.next, every attach overwrites dummy.next, so only the node from the last assignment survives, together with the nodes that follow it in its own list.

  • Returning dummy: the fake first node holds the extra value 0; return dummy.next.

  • Writing into an input while merging: when the merged result goes into one of the inputs, as in Merge Sorted Array (LC 88), fill it from the back (m + n - 1 down) with the larger head, so no unread value is overwritten.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

The statement gives two lists that are each sorted ("sorted in non-decreasing order") and asks for one sorted list made from both. Two sorted sequences read together in one pass is the merge walk: one pointer per list, always attaching the smaller head, needs no sort and no extra copy.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Invariant: the list from dummy.next to tail is sorted, and no unread node (from list1 or list2 on) is smaller than any node in it. The smaller head is the smallest unread node of both lists, so attaching it keeps the invariant, and only its own pointer moves.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

This problem's traps are in Common Pitfalls above.

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'll use Merge Two Sorted. Both lists are sorted, so I keep one pointer in each list, list1 and list2, and a tail that marks the end of the merged list, starting at a dummy node. At each step I compare the two heads and attach the smaller one to the tail. I move only the pointer of the list it came from, so each node is attached once, in sorted order. If the values are equal I take list1 first, so equal values keep their input order. The loop stops as soon as one list is empty. The trap is that the other list still has nodes, so I attach it with one assignment, tail.next equals list1 if list1 else list2. Then I return dummy.next, not dummy. Every node is visited once, so it is O(M + N) time, and I only relink existing nodes, so it is O(1) extra space."

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(M + N)

Look at the code: every pass of while list1 and list2 does one comparison, attaches one node to tail, and moves list1 or list2 one node forward. No pointer ever moves back, so the loop runs at most M + N times, each pass constant work. The line tail.next = list1 if list1 else list2 is one assignment, whatever the length of the list it attaches.

SPACE COMPLEXITY

O(1) extra

Look at the allocations: the code creates one node, dummy, and keeps the pointers list1, list2 and tail. Every other node of the result is a node of the input, relinked with tail.next = ..., so no list is copied and there is no recursion.

Formal Recurrence Relation

T(M, N) = (passes that move list1) + (passes that move list2) <= M + N = O(M + N)

Derivation Progression

Merge loop

while list1 and list2: 1 comparison + 1 attach per pass

Each pass moves list1 or list2 forward by one node, so it runs at most M + N times before one list is empty.

Attach the rest

tail.next = list1 if list1 else list2: 1 assignment

The remaining nodes are already linked, so one assignment attaches all of them, however many there are.

Total

M + N passes x O(1) + O(1) = O(M + N)

The pointers only move forward and each node is attached once; nothing is nested.

Variable Definitions

MMM

The number of nodes in list1

NNN

The number of nodes in list2

Memory Architecture & Bounds

🟣 Call Stack

O(1): no recursion

🔵 Auxiliary Heap

O(1): the one node dummy and the pointers list1, list2 and tail

🟢 Output Space

O(1) extra: the result is made of the input nodes, relinked

Boundary Best / Worst Cases

Best Case

O(min⁡(M,N))O(\min(M, N))O(min(M,N)): when every node of the shorter list is smaller than every node of the longer list, the loop ends once the shorter list is attached, and the rest is one assignment

Average Case

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

Worst Case

O(M+N)O(M + N)O(M+N): the two lists interleave, so the loop runs until the last node of one list is attached

Pointer Invariant Transition Progression

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

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

At scale the signal is two sorted inputs read together: joining two sorted tables or logs on a key, or merging sorted runs, where one cursor per input replaces an index or a hash table.

CONSTRAINTS & BOUNDS

0≤M,N≤500 \le M, N \le 500≤M,N≤50 in the problem, but the same walk reads inputs of any length once: M+NM + NM+N steps and no extra copy of the data, because each node is relinked, not copied. Both lists are read front to back exactly once, so they can arrive as two sorted streams. A comparison on val needs both inputs sorted in the same order; a list sorted descending, or one out-of-order node, breaks the invariant without any error.

FAANG PRODUCTION TRAPS & EDGE CASES

At scale: with two sorted streams, the attach of the other list means reading that stream to its end, not stopping when the shorter one closes. A tie must take the same side every time (<= takes list1), or equal keys swap order between runs and the merge is no longer stable.

Theory Context•Two Pointers & Sliding Window
EasyLC 21

Merge Two Sorted Lists (LeetCode 21)

Target Frequency:AmazonGoogleMeta

Two singly linked lists, list1 and list2, are each sorted from the smallest value to the largest. Equal values are allowed.

Join them into one list that is also sorted. Reuse the nodes that already exist: do not create new nodes to hold the values.

Return the head of the joined list. If both lists are empty, the answer is an empty list.

Worked Examples

Example 1
Input:list1 = [1,2,4], list2 = [1,3,4]
Output:[1,1,2,3,4,4]
list1
124
list2
134
Explanation: At every step the smaller of the two front values goes next. The two front values 1 and 1 are equal, so either node may go first; the result is the same.
Example 2
Input:list1 = [], list2 = []
Output:[]
Explanation: Both lists are empty, so the merged list is empty.
Example 3
Input:list1 = [], list2 = [0]
Output:[0]
Explanation: One list is empty, so the answer is the other list as it is.

⚖️Formal Constraints & Bounds

  • The number of nodes in both lists is in the range [0, 50].

  • -100 <= Node.val <= 100

  • list1 and list2 are each sorted in non-decreasing order.

Deep-Dive & Conceptual Insights

Core Insight

Both lists are sorted, so the smaller of the two front nodes is the smallest unread node of both lists: attach it and move only its pointer. Each node is attached once, in sorted order. When one list is empty, the other list is already sorted and linked, so one assignment attaches all of it.

Real-World Scenario & Production Applications

The merge step of merge sort, combining two sorted log files into one in time order, and the merge join that a database uses on two inputs already sorted by the join key.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Take The Smaller Head

Keep one pointer per list. The smaller head is the smallest unread node of both lists, so attach it to tail and move only the pointer of the list it came from.

Mathematical Recurrence / Code Invariant
while list1 and list2:
    if list1.val <= list2.val:
        tail.next = list1
        list1 = list1.next
    else:
        tail.next = list2
        list2 = list2.next
    tail = tail.next

Step-by-Step Execution Trace Table

Steplist1.vallist2.valBranchNode attached to tailMerged list so far
1111 <= 1: take from list11 (list1)1
2212 <= 1 is False: take from list21 (list2)1, 1
3232 <= 3: take from list12 (list1)1, 1, 2
4434 <= 3 is False: take from list23 (list2)1, 1, 2, 3
5444 <= 4: take from list1; list1 is now empty4 (list1)1, 1, 2, 3, 4
6(empty)4Loop ends. tail.next = list2 attaches the rest4 (list2)1, 1, 2, 3, 4, 4
7return dummy.next1, 1, 2, 3, 4, 4
Code Shape
Archetype Code Shape
Python
1dummy = ListNode(0)
2tail = dummy
3while list1 and list2:
4 if list1.val <= list2.val:
5 tail.next = list1
6 list1 = list1.next
7 else:
8 tail.next = list2
9 list2 = list2.next
10 tail = tail.next
11tail.next = list1 if list1 else list2
12return dummy.next

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Two pointers here walk two different sequences at once, not one: each sorted list has its own pointer. Both lists are sorted, so the first unread node of each list is the smallest one that list still has. The smaller of the two is therefore the smallest unread node of both lists, and it is the next node of the answer.

🗂️ The Analogy: Two Sorted Piles of Index Cards

Imagine two piles of index cards, each sorted with the smallest card on top. To build one sorted pile, look at the top card of each pile, move the smaller one to the new pile, and look again. When one pile is empty, the other pile is already sorted, so you place the whole pile on the new one in a single move.

🔑 The Key Idea in the Code

The loop stops as soon as either list is empty, so one more line must attach the list that is not empty. Leave it out and every node after the last comparison is lost.

  • No attach after the loop: while list1 and list2 stops as soon as either list is empty, so tail.next = list1 if list1 else list2 must still attach the other list. Without it LeetCode's first example returns [1,1,2,3,4] instead of [1,1,2,3,4,4]: the last 4 of list2 is never attached.

  • Looping while either list has a node: while list1 or list2 reads list1.val on an empty list and raises an error. Loop while both have a node, then attach the rest.

  • Forgetting to move tail: without tail = tail.next, every attach overwrites dummy.next, so only the node from the last assignment survives, together with the nodes that follow it in its own list.

  • Returning dummy: the fake first node holds the extra value 0; return dummy.next.

  • Writing into an input while merging: when the merged result goes into one of the inputs, as in Merge Sorted Array (LC 88), fill it from the back (m + n - 1 down) with the larger head, so no unread value is overwritten.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

The statement gives two lists that are each sorted ("sorted in non-decreasing order") and asks for one sorted list made from both. Two sorted sequences read together in one pass is the merge walk: one pointer per list, always attaching the smaller head, needs no sort and no extra copy.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Invariant: the list from dummy.next to tail is sorted, and no unread node (from list1 or list2 on) is smaller than any node in it. The smaller head is the smallest unread node of both lists, so attaching it keeps the invariant, and only its own pointer moves.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

This problem's traps are in Common Pitfalls above.

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'll use Merge Two Sorted. Both lists are sorted, so I keep one pointer in each list, list1 and list2, and a tail that marks the end of the merged list, starting at a dummy node. At each step I compare the two heads and attach the smaller one to the tail. I move only the pointer of the list it came from, so each node is attached once, in sorted order. If the values are equal I take list1 first, so equal values keep their input order. The loop stops as soon as one list is empty. The trap is that the other list still has nodes, so I attach it with one assignment, tail.next equals list1 if list1 else list2. Then I return dummy.next, not dummy. Every node is visited once, so it is O(M + N) time, and I only relink existing nodes, so it is O(1) extra space."

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(M + N)

Look at the code: every pass of while list1 and list2 does one comparison, attaches one node to tail, and moves list1 or list2 one node forward. No pointer ever moves back, so the loop runs at most M + N times, each pass constant work. The line tail.next = list1 if list1 else list2 is one assignment, whatever the length of the list it attaches.

SPACE COMPLEXITY

O(1) extra

Look at the allocations: the code creates one node, dummy, and keeps the pointers list1, list2 and tail. Every other node of the result is a node of the input, relinked with tail.next = ..., so no list is copied and there is no recursion.

Formal Recurrence Relation

T(M, N) = (passes that move list1) + (passes that move list2) <= M + N = O(M + N)

Derivation Progression

Merge loop

while list1 and list2: 1 comparison + 1 attach per pass

Each pass moves list1 or list2 forward by one node, so it runs at most M + N times before one list is empty.

Attach the rest

tail.next = list1 if list1 else list2: 1 assignment

The remaining nodes are already linked, so one assignment attaches all of them, however many there are.

Total

M + N passes x O(1) + O(1) = O(M + N)

The pointers only move forward and each node is attached once; nothing is nested.

Variable Definitions

MMM

The number of nodes in list1

NNN

The number of nodes in list2

Memory Architecture & Bounds

🟣 Call Stack

O(1): no recursion

🔵 Auxiliary Heap

O(1): the one node dummy and the pointers list1, list2 and tail

🟢 Output Space

O(1) extra: the result is made of the input nodes, relinked

Boundary Best / Worst Cases

Best Case

O(min⁡(M,N))O(\min(M, N))O(min(M,N)): when every node of the shorter list is smaller than every node of the longer list, the loop ends once the shorter list is attached, and the rest is one assignment

Average Case

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

Worst Case

O(M+N)O(M + N)O(M+N): the two lists interleave, so the loop runs until the last node of one list is attached

Pointer Invariant Transition Progression

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

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

At scale the signal is two sorted inputs read together: joining two sorted tables or logs on a key, or merging sorted runs, where one cursor per input replaces an index or a hash table.

CONSTRAINTS & BOUNDS

0≤M,N≤500 \le M, N \le 500≤M,N≤50 in the problem, but the same walk reads inputs of any length once: M+NM + NM+N steps and no extra copy of the data, because each node is relinked, not copied. Both lists are read front to back exactly once, so they can arrive as two sorted streams. A comparison on val needs both inputs sorted in the same order; a list sorted descending, or one out-of-order node, breaks the invariant without any error.

FAANG PRODUCTION TRAPS & EDGE CASES

At scale: with two sorted streams, the attach of the other list means reading that stream to its end, not stopping when the shorter one closes. A tie must take the same side every time (<= takes list1), or equal keys swap order between runs and the merge is no longer stable.

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