Skip to content

Two pointers and sliding window

Each index only moves one way; a window also needs a rule that changes one way as it grows.

Predict first

Before you read: predict

A box office counts net tickets per minute; a minute with more returns than sales is negative. It looks for the shortest run of consecutive minutes that sold at least 100 tickets net:

What is minutes?

Code for this question
export function shortestRun(net, target) {  let best = Infinity, sum = 0, left = 0;  for (let right = 0; right < net.length; right++) {    sum += net[right];    while (sum >= target) {      best = Math.min(best, right - left + 1);      sum -= net[left++];    }  }  return best;}export const minutes = shortestRun([-30, 90, -10, 20, 20, 40, 20], 100);
Pick one

The 30-second version

Two pointers walk a list with two indices that only move forward or inward, so a pass is O(n), not O(n²). From both ends they find pairs in a sorted list. As a window they grow on the right and shrink on the left, which needs a rule that changes one way as the window grows.

Each index only moves one way; a window also needs a rule that changes one way as it grows.

How it works

Step 1 of 10

Two indices, each moving one way. Neither moves back, so a pass touches each element a bounded number of times: O(n) (dsa-big-o-js-costs step 1).

Analogy: A river cable car's control room on a busy Saturday (aerial ropeways)

The cable car crosses the river in 10-seat cabins; the queue marshal fills them and the controller watches the line.

At the cable car In the code
the marshal lines the waiting groups up by size, smallest first, and offers one cabin to the smallest and the largest group together pair from both ends of a sorted list (step 2)
too few riders: the next bigger small group steps forward; too many: the largest group waits and the next smaller big group is tried move the end that can close the gap (step 3)
the "riders on the line" board: the controller adds each cabin that boards and subtracts each that unloads, never recounting the whole line keep the summary in step (step 5)
the controller's one-minute frame over the departure log: each new departure enters at the bottom, departures a minute or more older slide off the top a window over time (step 10)

In real terms: two pointers replace a nested loop with one pass by never letting an index move back.

Breaks down when: a cabin never carries fewer than zero riders, but a list of net changes (riders boarding minus riders unloading) has negative values, and then a window's goal no longer changes one way (step 9).

Contrasting case: three groups in the queue. The marshal tries every pairing by eye; the pattern pays as the queue grows.

Examples, run for real

4.1 ExamHall EXH-4930: tab-switch bursts, a filter per switch

ExamHall flags a candidate when 5 or more tab switches fall inside any 60 seconds. Its first check filtered the whole log once per switch:

const flagged = (times) => times.some((t) => times.filter((u) => u >= t && u - t < 60000).length >= 5);

On long logs with no burst, the proctor queue fell behind (EXH-4930). MR !5230 walks one window over the sorted times instead (step 10). Both were counted on logs with one switch every 15 s: neither finds a burst, so both read to the end, the worst case:

4.1 EXH-4930 filter per switch, 500 switches, one every 15 s: 250,500 time reads · no burst
4.1 MR !5230 one window, same log: 1,992 time reads · no burst
4.1 EXH-4930 filter per switch, 1,000 switches, one every 15 s: 1,001,000 time reads · no burst
4.1 MR !5230 one window, same log: 3,992 time reads · no burst
4.1 doubling the switches: filter ×4.00 · one window ×2.00; a log with 5 switches inside 40 s: both flag it

The filter reads the log once per switch, so doubling the log quadruples the reads; the window's indices only move forward, so its reads double (steps 1, 8).

4.2 Chat: the busiest five minutes

A chat app shows each room's busiest five consecutive minutes. Re-adding every five-minute window reads a minute up to five times; a running sum adds the new minute and drops the one five back (step 6):

4.2 60 minutes: re-adding each window 280 reads · running sum 115 reads · both pick minutes 25–29 (62 messages)
4.2 120 minutes: re-adding each window 580 reads · running sum 235 reads · both pick minutes 25–29 (62 messages)

Both pick the same minutes; re-adding reads most minutes five times, the running sum at most twice.

Try it

Answer first, then check. Nothing is saved.

Question 1 of 8

A chat app highlights the longest stretch of consecutive messages in which no sender appears twice:

What is longest?

Code for this question
export function longestNoRepeat(senders) {  const last = new Map();  let left = 0, best = 0;  for (let i = 0; i < senders.length; i++) {    if (last.has(senders[i])) left = Math.max(left, last.get(senders[i]) + 1);    last.set(senders[i], i);    best = Math.max(best, i - left + 1);  }  return best;}export const longest = longestNoRepeat(['bo', 'cy', 'bo', 'bo', 'ana', 'eli', 'cy']);
Pick one

Question 2 of 8

A payments ledger looks for two refunds that add up to a disputed amount. The refunds are sorted, smallest first, and the search walks in from both ends:

In what order does the loop run them, top first?

Code for this question
export function pairFor(amounts, target) {  let lo = 0, hi = amounts.length - 1;  while (lo < hi) {    /* the four lines, in order */  }  return null;}

Your order

  1. 1empty
  2. 2empty
  3. 3empty
  4. 4empty

Tap the lines in the order you expect.

Question 3 of 8

A video player keeps the longest run of consecutive segments that fits its 500 MB buffer. The first 300 segments are 10 MB each, the other 500 are 1 MB each:

Counting every read of an element of sizes (each sizes[…]), how many reads does longestFit(sizes, 500) make?

Code for this question
export function longestFit(sizes, cap) {  let left = 0, total = 0, best = 0;  for (let right = 0; right < sizes.length; right++) {    total += sizes[right];    while (total > cap) total -= sizes[left++];    best = Math.max(best, right - left + 1);  }  return best;}export const sizes = Array.from({ length: 800 }, (_, i) => (i < 300 ? 10 : 1));
Pick one

Question 4 of 8

ExamHall looks for the shortest stretch of a candidate's event log that holds three tab switches (1 is a tab switch, 0 an answer saved):

What is stretch?

Code for this question
export function shortestStretch(events, need) {  let best = null, count = 0, left = 0;  for (let right = 0; right < events.length; right++) {    count += events[right];    while (count >= need) {      if (!best || right - left < best[1] - best[0]) best = [left, right];      count -= events[left++];    }  }  return best;}export const stretch = shortestStretch([0, 1, 0, 0, 0, 1, 1, 1, 0, 0], 3);
Pick one

Question 5 of 8

the interviewer says: "Given the sorted times of one candidate's tab switches, in milliseconds, and a window length, return the largest number of switches inside any one window. Two switches share a window when their times differ by less than the window length. Do it in one pass." You have 15 minutes and a plain editor, and ExamHall's burst flag is the use case.

Before you type: say your test list out loud.

Name at least five tests, each with its input and what it expects.

Question 6 of 8

the interviewer says: "Given the sorted times of one candidate's tab switches, in milliseconds, and a window length, return the largest number of switches inside any one window. Two switches share a window when their times differ by less than the window length. Do it in one pass." You have 15 minutes and a plain editor, and ExamHall's burst flag is the use case.

Now write it, in 15 minutes, from a blank file that exports busiestWindow.

Question 7 of 8

A box office counts the ways to fill a 10-seat row with two group bookings. The bookings are sorted by size, smallest first, and the count walks in from both ends:

What is ways?

Code for this question
export const sizes = [2, 3, 5, 5, 5, 5, 7, 8];export function waysToFill(sizes, seats) {  let lo = 0, hi = sizes.length - 1, ways = 0;  while (lo < hi) {    const sum = sizes[lo] + sizes[hi];    if (sum === seats) { ways++; lo++; hi--; }    else if (sum < seats) lo++;    else hi--;  }  return ways;}export const ways = waysToFill(sizes, 10);
Pick one

Question 8 of 8

A chat app's draft looks for the longest stretch of consecutive messages from at most two senders:

What is stretch?

Code for this question
export function longestTwoSenders(ids) {  const counts = new Map();  let left = 0, distinct = 0, from = 0, to = 0;  for (let right = 0; right < ids.length; right++) {    if (!counts.has(ids[right])) distinct++;    counts.set(ids[right], (counts.get(ids[right]) ?? 0) + 1);    while (distinct > 2) {      const out = ids[left++];      counts.set(out, counts.get(out) - 1);      if (counts.get(out) === 0) distinct--;    }    if (right + 1 - left > to - from) [from, to] = [left, right + 1];  }  return ids.slice(from, to).join(' ');}export const stretch = longestTwoSenders(['cy', 'dee', 'cy', 'ana', 'ana', 'dee', 'cy']);
Pick one

Go deeper

Pro tips
  1. Before sliding a window, check that the condition changes monotonically as the window grows; with negative numbers it often does not. When: the first minute of a subarray question, before any code (step 9). Verify (RAN, tips.mjs): (The curriculum row's tip, [SELF].)
    tip 1 at least 70 · all positive [20, 10, 40, 30, 10]: window 2, every run 2 · with a negative [40, -40, 10, 50, 30]: window 5, every run 2
  2. Say the array corner cases out loud: an empty list, one or two values, repeated values. When: before running your pointers in an interview. Verify (RAN, tips.mjs):
    tip 2 pairs adding to 10 · empty null · one value null · two values [0, 1] · repeated values [0, 2]
Source

Vendor pages change, so its claims are labelled as of 2026-10-17; every behaviour and count this lesson grades is executed on Node 24.

  • Tech Interview Handbook, Array cheatsheet (the curriculum row's source), https://www.techinterviewhandbook.org/algorithms/array/ (tih-array.txt). As of 2026-10-17.
    • Sliding window (steps 1, 4, 8): "In a sliding window, the two pointers usually move in the same direction will never overtake each other. This ensures that each value is only visited at most twice and the time complexity is still O(n)." Its examples include "Longest Substring Without Repeating Characters" (step 7) and "Minimum Size Subarray Sum" (steps 4, 9).
    • Two pointers (steps 2, 3): "Two pointers is a more general version of sliding window where the pointers can cross each other and can be on different arrays." and "Can you sort the array? Sometimes sorting the array first may significantly simplify the problem."
    • Precomputation (step 9's way out): "For questions where summation or multiplication of a subarray is involved, pre-computation using hashing or a prefix/suffix sum/product might be useful."
    • Its corner cases (tip 2): "Empty sequence", "Sequence with 1 or 2 elements", "Sequence with repeated elements".
  • The page says nothing about negative values or about a rule that must change one way; step 9 rests on execution alone.

Checked against the tools and docs as of 2026-10-17.