21 Microsoft JavaScript Frontend Developer Interview Questions & Answers

microsoft icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: August 28, 2026)

1. Find the maximum profit from one stock purchase and one later sale.CodingEasyMicrosoft

Question Details

Implement maxSingleTradeProfit(prices) in JavaScript. prices is an array of 1 through 100,000 non-negative safe integers in chronological order; duplicate prices are allowed. Choose at most one buy and one later sell, never sell before buying, and return the greatest non-negative profit; return 0 when no profitable trade exists. Do not mutate or sort the input, and invalid input is outside scope. Examples: maxSingleTradeProfit([7,1,5,3,6,4]) returns 5, while [7,6,4,3,1] returns 0. Target O(n) time and O(1) auxiliary space.

Short Interview Answer (30-60 seconds)

I would scan the prices from left to right and keep two values: the lowest price seen so far and the best profit found so far. For each price, I update the minimum when the price is lower. Otherwise, I calculate the profit from selling at the current price and update the best profit if it is larger. This works because the minimum always comes from the current or an earlier day. The solution takes O(n) time and O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

We are given stock prices in chronological order. We may buy once and sell once later. We want the largest possible profit, but we may also choose not to trade, so the answer can never be negative. The main idea is to remember the lowest price seen so far. Then each later price can be treated as a possible selling price. We compare its possible profit with the best profit found so far. This avoids checking every possible buy and sell pair and gives the required one-pass solution.

Useful Questions to Ask the Interviewer
  1. Should I return only the maximum profit, not the buy and sell indices?
  2. If every possible trade gives no positive profit, should I return 0?
  3. Can I assume the input follows the stated constraints and does not need validation?
Find the maximum profit from one stock purchase and one later sale. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an array called prices. It contains from 1 to 100,000 non-negative safe integers in chronological order. A buy must happen before a sell. We return only the greatest non-negative profit. We do not return the buy or sell indices. We also do not sort or change the input array.

For the example prices = [7, 1, 5, 3, 6, 4], the best trade is to buy at price 1 at index 1 and sell later at price 6 at index 4. The profit is 6 - 1 = 5, so the function returns 5.

2. Choose the algorithm and state

We scan from left to right. We keep minPrice, which is the lowest price seen so far, and maxProfit, which is the largest valid profit seen so far. The central invariant is that after processing index i, minPrice is the lowest price in prices[0..i]. This means a profit calculated at the current index never uses a future buying price.

3. Initialize the state

The code starts with minPrice = Infinity and maxProfit = 0. Infinity lets the first real price become the first minimum. Starting maxProfit at 0 guarantees that the returned answer is never negative. If no profitable trade exists, maxProfit remains 0.

4. Walk through the example

At index 0, the price is 7. Since 7 < Infinity, minPrice becomes 7. maxProfit stays 0.

At index 1, the price is 1. Since 1 < 7, minPrice becomes 1. maxProfit stays 0.

At index 2, the price is 5. It is not a new minimum, so the possible profit is 5 - 1 = 4. Since 4 is larger than maxProfit, maxProfit becomes 4.

At index 3, the price is 3. The possible profit is 3 - 1 = 2. This is smaller than 4, so maxProfit stays 4.

At index 4, the price is 6. The possible profit is 6 - 1 = 5. This is larger than 4, so maxProfit becomes 5.

At index 5, the price is 4. The possible profit is 4 - 1 = 3. This is smaller than 5, so maxProfit stays 5.

After the loop finishes, the function returns 5.

5. Explain why the result is correct

At every index, minPrice stores the lowest price seen up to that point. Therefore, when the current price is treated as a possible sell price, minPrice gives the cheapest valid buy price available so far. The algorithm compares that valid profit with maxProfit. Because prices are processed in chronological order, the algorithm never sells before buying. Taking the largest profit over all possible selling days gives the best single-trade result.

6. Explain the JavaScript implementation

The function creates minPrice and maxProfit, then loops through the array once. For each price, it first checks whether that price is a new minimum. If it is, minPrice is updated. Otherwise, the function calculates price - minPrice and updates maxProfit when the new profit is larger. Finally, it returns maxProfit. The array is only read, so the input is not mutated or sorted.

7. Explain complexity and edge cases

The loop visits each price once, so the time complexity is O(n). The algorithm stores only a fixed number of variables, so the auxiliary space complexity is O(1). A one-element array returns 0. Strictly decreasing prices return 0. Equal prices also return 0. Duplicate prices work correctly because the algorithm only tracks the smallest price seen so far and the largest valid profit.

Key Insight / Why This Solution Works

The key insight is that for every possible selling day, we only need the cheapest price available up to that day. We therefore keep a running minimum called minPrice and a running best answer called maxProfit. The invariant is: after processing index i, minPrice is the lowest value in prices[0..i], and maxProfit is the largest valid non-negative profit found among the processed prices. This removes the need to compare every possible buy and sell pair with two nested loops.

Code
function maxSingleTradeProfit(prices) {
  // Start above every valid price so the first price becomes the first minimum.
  let minPrice = Infinity;

  // Start at 0 because choosing no profitable trade must return 0.
  let maxProfit = 0;

  // Process prices from left to right in chronological order.
  for (let i = 0; i < prices.length; i++) {
    const price = prices[i];

    // A lower price becomes the best buying price seen so far.
    if (price < minPrice) {
      minPrice = price;
    } else {
      // Selling at the current price is valid because minPrice came from
      // the current or an earlier position in the chronological scan.
      const profit = price - minPrice;

      // Keep the greatest non-negative profit found so far.
      if (profit > maxProfit) {
        maxProfit = profit;
      }
    }
  }

  // If no profitable trade exists, maxProfit remains 0.
  return maxProfit;
}

// Run the same examples shown in the approved diagram.
console.log(maxSingleTradeProfit([7, 1, 5, 3, 6, 4])); // 5
console.log(maxSingleTradeProfit([7, 6, 4, 3, 1])); // 0
Time & Space Complexity

Time complexity is O(n) because the algorithm goes through the prices array once. If there are n prices, each one needs only a constant amount of work. Auxiliary space is O(1) because the algorithm stores only a fixed number of values such as minPrice, maxProfit, price, and profit. The extra memory does not grow when the input gets larger.

Where it is used

This running-minimum pattern is useful when values arrive in order and each later value must be compared with the best earlier value. Similar logic can be used in financial analysis, time-series monitoring, and problems that ask for the largest increase from an earlier measurement to a later measurement without storing every possible pair.

Why Interviewers Ask This

This problem checks whether a candidate can replace a simple O(n^2) pair-checking idea with a one-pass solution. It tests whether you understand chronological constraints, maintain useful running state, and keep the buy before the sell. It also checks whether you handle no-profit cases, avoid unnecessary sorting or extra memory, write correct JavaScript, maintain a clear invariant, and explain the O(n) time and O(1) auxiliary space accurately.

Common interview mistakes

A common mistake is comparing prices in a way that allows selling before buying. Another mistake is sorting the array, which destroys the original chronological order. Candidates may also return a negative result for decreasing prices instead of keeping maxProfit at 0. Using two nested loops is correct but takes O(n^2) time and misses the required O(n) target. Another mistake is updating the running state in the wrong order and using a future price as the buy price.

Interview tip

State the invariant while you code: minPrice is always the lowest price seen so far, and maxProfit is always the best valid profit seen so far. Then walk through [7, 1, 5, 3, 6, 4] and show exactly when minPrice becomes 1 and when maxProfit changes from 0 to 4 and then to 5.

Interviewer may ask next
How would the solution change if we also had to return the buy and sell indices?

I would keep the same O(n) scan. Along with minPrice, I would store the index where that minimum was found. When a new maxProfit is found, I would save that minimum index as the best buy index and the current index as the best sell index. If no profitable trade exists, the required no-trade return format would need to be defined. Time remains O(n), auxiliary space remains O(1), and the chronological rule is still preserved.

How would this work if prices arrived one at a time as a stream?

The same algorithm still works because it only needs the current price, minPrice, and maxProfit. For each new price, I update minPrice if the value is smaller. Otherwise, I calculate the possible profit and update maxProfit if needed. I do not need to store the full history. After processing n streamed prices, total time is O(n) and auxiliary space is O(1). The tradeoff is that only the summary state is kept, not all earlier prices.

2. Implement a stack that returns its minimum in constant time.CodingMediumMicrosoft

Question Details

Implement class MinStack in JavaScript with methods push(value), pop(), top(), and getMin(). Values are signed 32-bit integers, duplicate minimum values are allowed, and pop, top, and getMin are called only while the stack is non-empty. push adds one value; pop removes the top value and need not return it; top returns the current top; and getMin returns the smallest current value. Do not rescan all stored values during any operation or use an external collection library. Example: after push(-2), push(0), push(-3), getMin() returns -3; after pop(), top() returns 0 and getMin() returns -2. Every method must run in O(1) time, with O(n) retained space overall.

Short Interview Answer (30-60 seconds)

I would use two stacks. The main stack stores every pushed value. A second stack, minStack, stores the minimum value for each matching stack depth. On push, I add the new value to the main stack and add the smaller of that value and the previous minimum to minStack. On pop, I pop both stacks. Then top reads the main stack, while getMin reads minStack. Every operation is O(1), and the retained space is O(n).

Detailed Explanation

See the Code while reading this explanation.

The task is to build a stack that can return both its top value and its smallest current value without searching through all stored values. We need push, pop, top, and getMin. The diagram uses two synchronized stacks. The main stack stores the real values. minStack stores the minimum value for every matching stack depth. This means its top always tells us the current minimum. Each operation does only a constant amount of work, while the two stacks together use O(n) retained space.

Useful Questions to Ask the Interviewer
  1. Can I assume pop(), top(), and getMin() are called only when the stack is non-empty? Yes. The problem guarantees this.
  2. Do duplicate minimum values need to work correctly? Yes. Duplicate minimum values are allowed.
Implement a stack that returns its minimum in constant time. diagram
How to Explain It in an Interview
1. Understand the required behavior

The class has four methods. push(value) adds one signed 32-bit integer. pop() removes the current top value and does not need to return it. top() returns the current top value. getMin() returns the smallest value currently stored. We cannot scan the full stack during any operation. Every method must run in O(1) time.

2. Choose two synchronized stacks

The main stack stores every value. minStack has one entry for every entry in the main stack. Each minStack entry stores the minimum value at that exact depth. Therefore, both stacks always have the same length. The top of minStack is always the minimum of all values currently in the main stack.

3. Push while recording the minimum at each depth

When both stacks are empty, push(-2) adds -2 to the main stack. There is no previous minimum, so minStack also receives -2. The state is stack = [-2] and minStack = [-2]. Next, push(0) adds 0 to the main stack. The previous minimum is -2, so min(0, -2) is -2. The state becomes stack = [-2, 0] and minStack = [-2, -2]. Then push(-3) adds -3. min(-3, -2) is -3, so the state becomes stack = [-2, 0, -3] and minStack = [-2, -2, -3].

4. Walk through the required example

After push(-2), push(0), and push(-3), getMin() reads the top of minStack and returns -3. Then pop() removes the top entry from both stacks. The main stack becomes [-2, 0], and minStack becomes [-2, -2]. Now top() reads the main stack and returns 0. Finally, getMin() reads the top of minStack and returns -2.

5. Explain why the result is correct

The invariant is that minStack[i] is the minimum value among stack[0] through stack[i]. Every push preserves this invariant because it stores the smaller of the new value and the previous minimum. Every pop removes the same depth from both stacks. Therefore, after a pop, the new top of minStack is exactly the minimum for the remaining main stack. Duplicate minimums also work because every depth stores its own minimum value.

6. Explain the JavaScript implementation

The constructor creates two empty arrays. push(value) first adds value to stack. It then calculates the minimum for the new depth. If minStack is empty, that minimum is value itself. Otherwise, it uses Math.min(value, previousMinimum). It pushes that result to minStack. pop() removes one entry from each stack. top() returns the last main-stack value. getMin() returns the last minStack value.

7. Explain complexity and edge cases

push(), pop(), top(), and getMin() each perform only a constant number of array operations, so each method runs in O(1) time. The main stack can contain n entries, and minStack contains exactly one matching entry for each depth, so retained space is O(n). Duplicate minimums work naturally. Negative values, positive values, and zero also work. The problem guarantees that pop(), top(), and getMin() are never called on an empty stack.

Key Insight / Why This Solution Works

Use two synchronized stacks. The main stack stores the actual values. minStack stores the minimum value for each corresponding depth. The central invariant is: minStack[i] equals the minimum of stack[0..i]. On push, store the new value in stack and store min(value, previous minimum) in minStack. If this is the first value, store the value itself as the minimum. On pop, remove one entry from both stacks. Because both stacks remain aligned by depth, the top of minStack is always the current minimum. This avoids an O(n) rescan.

Code
class MinStack {
  constructor() {
    // Store every value pushed into the stack.
    this.stack = [];

    // Store the minimum value for every matching stack depth.
    // minStack[i] is the minimum of stack[0..i].
    this.minStack = [];
  }

  push(value) {
    // Add the real value to the main stack.
    this.stack.push(value);

    // The first value is automatically the minimum at depth 0.
    if (this.minStack.length === 0) {
      this.minStack.push(value);
      return;
    }

    // Compare the new value with the minimum from the previous depth.
    const previousMin = this.minStack[this.minStack.length - 1];
    const currentMin = Math.min(value, previousMin);

    // Record the minimum for this new stack depth.
    // This also handles duplicate minimum values correctly.
    this.minStack.push(currentMin);
  }

  pop() {
    // Both arrays represent the same depth, so remove one entry from each.
    // The problem guarantees that pop() is called only when non-empty.
    this.stack.pop();
    this.minStack.pop();
  }

  top() {
    // Return the actual value at the top of the main stack.
    return this.stack[this.stack.length - 1];
  }

  getMin() {
    // The top of minStack is the minimum at the current depth.
    return this.minStack[this.minStack.length - 1];
  }
}

// Run the exact example from the diagram and question.
const minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);

console.log(minStack.getMin()); // -3

minStack.pop();
console.log(minStack.top()); // 0
console.log(minStack.getMin()); // -2
Time & Space Complexity

Every method runs in O(1) time. push() performs one push to the main stack, one constant-time minimum calculation, and one push to minStack. pop() performs one pop from each stack. top() and getMin() directly read the last array element. The retained space is O(n). If the main stack contains n values, minStack also contains n minimum values, so total stored data grows linearly with n.

Where it is used

This pattern is useful when a stack must answer a running aggregate query immediately. A minimum stack is one example. Similar synchronized auxiliary stacks can maintain a maximum or another value that can be updated from the previous stack state in constant time. It is useful when queries happen often and rescanning all stored values would be too slow.

Why Interviewers Ask This

This problem tests whether you can trade extra memory for faster queries. The interviewer wants to see whether you can maintain useful information incrementally instead of recomputing it. It also checks whether you can define and preserve an invariant, keep two data structures synchronized, handle duplicate minimum values, write correct JavaScript array operations, and explain why every method is O(1) while the retained space is O(n).

Common interview mistakes

A common mistake is storing only the newest minimum instead of the minimum for every stack depth. Then the previous minimum can be lost after a pop. Another mistake is pushing into the main stack without also pushing a matching minimum into minStack, which breaks the depth alignment. Candidates may also pop only one of the two stacks. Another mistake is scanning the entire main stack inside getMin(), which makes getMin() O(n). Finally, claiming O(1) total space is incorrect because both arrays grow with the number of stored values.

Interview tip

State the invariant first: minStack[i] stores the minimum of stack[0..i]. Then trace the two arrays together for -2, 0, and -3. This makes it easy to show why getMin() is a direct O(1) lookup and why popping both stacks restores the previous minimum correctly.

Interviewer may ask next
How does this approach handle duplicate minimum values?

It handles them automatically because minStack stores a minimum for every stack depth. For example, if the previous minimum is -3 and another -3 is pushed, Math.min(-3, -3) is still -3, so another -3 is stored at the new depth. Popping one copy removes one entry from both stacks, while the earlier -3 remains recorded. The time for each operation stays O(1), and retained space stays O(n).

How would you extend this design to also return the maximum in O(1) time?

Add a third synchronized stack called maxStack. For each push, store the larger of the new value and the previous maximum at the matching depth. On pop, remove one entry from stack, minStack, and maxStack. getMax() then returns the top of maxStack. The invariant is that maxStack[i] stores the maximum of stack[0..i]. All operations remain O(1). Retained space remains O(n), but the constant amount of memory per stored value increases.

3. Sort up to one million bounded integers by counting occurrences.CodingHardMicrosoft

Question Details

Implement countingSort(values, maxValue) in JavaScript. values is an owned array of 0 through 1,000,000 integers, every entry lies in the inclusive range 0..maxValue, and maxValue is a safe integer from 0 through 1,000,000; duplicates and empty input are allowed. Build an occurrence array indexed by value and return a new array containing all input values in nondecreasing order. Do not mutate values, call Array.prototype.sort, or create records for values outside the stated range; inputs outside the schema need not be handled. Example: countingSort([4,1,3,1,0], 4) returns [0,1,1,3,4]. Target O(n + maxValue) time and O(maxValue) auxiliary space in addition to the result.

Short Interview Answer (30-60 seconds)

I would use counting sort because every value is a bounded integer from 0 through maxValue. I create a count array where count[v] stores how many times value v appears. I count every input value, then scan values from 0 through maxValue and write each value into a new result array count[v] times. This preserves duplicates and produces nondecreasing order without changing the input. The time is O(n + maxValue), with O(maxValue) auxiliary space besides the result.

Detailed Explanation

See the Code while reading this explanation.

We need to return a new array containing the same numbers arranged from smallest to largest. Every number is between 0 and maxValue, so we can use each number itself as a position in a count array. First, we count how many times every number appears. Then we visit the possible values from 0 upward and copy each value into the result the recorded number of times. This naturally keeps duplicates and gives sorted order. The original values array stays unchanged, and we do not use the built-in sort method.

Useful Questions to Ask the Interviewer
  1. Can I rely on every entry being an integer in the inclusive range 0..maxValue?
  2. Should I keep the original values array unchanged?
  3. Is O(maxValue) auxiliary memory, not counting the returned array, acceptable?
Sort up to one million bounded integers by counting occurrences. diagram
How to Explain It in an Interview
1. Understand the input and required output

The function receives values and maxValue. values may be empty and may contain duplicate numbers. Every entry is an integer from 0 through maxValue. maxValue is a safe integer from 0 through 1,000,000. We must return a new array containing all input values in nondecreasing order. We must not change values, call Array.prototype.sort, or create count entries outside the stated range.

For the diagram example, values is [4, 1, 3, 1, 0] and maxValue is 4. The required result is [0, 1, 1, 3, 4].

2. Choose the algorithm and data structure

I use counting sort. The main data structure is an occurrence array named count. Its index represents an input value. count[v] means how many times value v appears in values.

The important invariant is that after the counting loop finishes, count[v] is exactly the number of occurrences of v in the input. Because the reconstruction loop visits v from 0 through maxValue, writing each v exactly count[v] times produces all original values in sorted order.

3. Initialize and count occurrences

For maxValue = 4, the count array has length maxValue + 1 = 5. It starts as [0, 0, 0, 0, 0], with indices 0, 1, 2, 3, and 4.

We process the input [4, 1, 3, 1, 0]. For each current value, we increment count[currentValue]. After all five input elements are counted, count is [1, 2, 0, 1, 1]. This means 0 appears once, 1 appears twice, 2 appears zero times, 3 appears once, and 4 appears once.

4. Walk through reconstruction

We create a new result array with the same length as values. We also keep idx as the next position to fill.

At i = 0, count[0] is 1, so we write 0 once. The result begins [0].

At i = 1, count[1] is 2, so we write 1 twice. The filled part of the result becomes [0, 1, 1].

At i = 2, count[2] is 0, so the code writes nothing.

At i = 3, count[3] is 1, so we write 3 once. The filled part becomes [0, 1, 1, 3].

At i = 4, count[4] is 1, so we write 4 once. The final result is [0, 1, 1, 3, 4].

5. Explain why the result is correct

Every input value increments exactly one matching count entry. Therefore the count array records every input occurrence exactly once. During reconstruction, each value i is written exactly count[i] times. No original occurrence is lost, and no extra value is added. Because i is visited from 0 through maxValue, every smaller value is written before every larger value. Therefore the returned array contains the same values in nondecreasing order.

6. Explain the JavaScript implementation

The code first reads values.length into n. Empty input returns a new empty array. Otherwise it creates count with maxValue + 1 zeroes. The first loop increments count[values[i]] for every input element. The code then creates result and starts idx at 0. The second outer loop visits i from 0 through maxValue. It reads c = count[i]. When c is greater than zero, the inner loop writes i exactly c times and advances idx after every write. Finally, it returns result without changing values.

7. Explain complexity and edge cases

Let n be values.length. Counting takes O(n) time. The reconstruction loop visits every possible value from 0 through maxValue and performs exactly n total writes into the result. The total time is O(n + maxValue). The count array uses O(maxValue) auxiliary space. The result itself uses O(n) output space and is separate from that auxiliary-space bound.

Relevant edge cases shown by the diagram are empty input, all values being the same, maxValue equal to 0, a large maxValue up to 1,000,000, and the stated guarantee that every input value is in 0..maxValue.

Key Insight / Why This Solution Works

The key insight is that every input is an integer from a known bounded range. That lets us use the value itself as an array index instead of comparing values with one another. count[v] stores the exact frequency of v. After counting, the invariant is that count[v] equals the number of times v appears in values. We then visit possible values in increasing order and emit each one exactly count[v] times. This reconstructs the same multiset in nondecreasing order and gives the required O(n + maxValue) time.

Code
function countingSort(values, maxValue) {
  const n = values.length;

  // Empty input is already sorted, so return a new empty array.
  if (n === 0) return [];

  // count[i] stores how many times value i appears.
  // maxValue + 1 gives one slot for every legal value from 0 through maxValue.
  const count = new Array(maxValue + 1).fill(0);

  // Count each input occurrence without changing the original values array.
  for (let i = 0; i < n; i++) {
    count[values[i]]++;
  }

  // Build a separate result array so the input remains unchanged.
  const result = new Array(n);
  let idx = 0;

  // Visit possible values in increasing order.
  for (let i = 0; i <= maxValue; i++) {
    const c = count[i];

    // Only values with a positive count need to be written.
    if (c > 0) {
      // Write value i exactly c times, preserving all duplicates.
      for (let k = 0; k < c; k++) {
        result[idx++] = i;
      }
    }
  }

  // Every input occurrence has now been written in nondecreasing order.
  return result;
}

// Run the exact example from the diagram.
const values = [4, 1, 3, 1, 0];
const maxValue = 4;
console.log(countingSort(values, maxValue)); // [0, 1, 1, 3, 4]
Time & Space Complexity

Let n be values.length. The first loop processes n input elements, so counting costs O(n). The reconstruction loop visits every possible integer from 0 through maxValue, which costs O(maxValue), and its inner loops perform n total writes because every input occurrence is written once. The total time is therefore O(n + maxValue). The count array has maxValue + 1 entries, so auxiliary space is O(maxValue). The returned result has n entries and uses O(n) output space, which is separate from the requested auxiliary-space bound.

Where it is used

Counting sort is useful when items are integers from a known and reasonably bounded range. Examples include sorting ratings, ages, small numeric identifiers, bucket numbers, or other integer categories where using one frequency slot for each possible value is practical.

Why Interviewers Ask This

This problem checks whether you recognize that a bounded integer range makes counting sort appropriate. It tests whether you can map values to frequency slots correctly, size the occurrence array correctly, preserve duplicate values, avoid changing the input, and reconstruct the result in the required order. It also tests JavaScript implementation skills and whether you can justify the invariant and state the O(n + maxValue) time and O(maxValue) auxiliary-space bounds accurately.

Common interview mistakes

One common mistake is creating only maxValue count slots instead of maxValue + 1, which leaves no slot for the value maxValue. Another is confusing input indices with values and incrementing the wrong count entry. Candidates may also forget to emit duplicate values count[i] times. Mutating values or calling Array.prototype.sort breaks the required contract. It is also incorrect to claim O(n) time while ignoring the scan through 0..maxValue, or to call the growing count array O(1) auxiliary space.

Interview tip

Define count[i] before writing the loops: index i is the actual value, and count[i] is its frequency. Then explain that scanning those indices from 0 upward is what makes the reconstructed result sorted.

Interviewer may ask next
What changes if maxValue is extremely large compared with the number of input values?

The dense count array may use too much memory because it still needs maxValue + 1 slots. One possible change is to store frequencies only for values that actually appear, then order those distinct values before reconstruction. If there are d distinct values, counting with a hash map takes O(n) expected time, ordering the d keys takes O(d log d), and reconstruction takes O(n). The total expected time becomes O(n + d log d), with O(d) auxiliary space plus the O(n) result. Correctness is preserved because every frequency is still recorded and values are emitted in increasing key order. The tradeoff is lower memory for sparse ranges but extra ordering work.

How would the solution work if the values arrived as a stream?

If maxValue is still known and bounded, the same count array can be kept while the stream arrives. For each streamed value v, increment count[v]. There is no need to store the complete input. After the stream ends, scan 0 through maxValue and emit each value count[v] times. The total time remains O(n + maxValue), and auxiliary space remains O(maxValue). Correctness is unchanged because count[v] still records every occurrence. The main tradeoff is that the final sorted output cannot be completed until the stream has ended.

4. Determine whether a non-clean string is a palindrome.CodingEasyMicrosoft

Question Details

Implement isPalindrome(text) in modern JavaScript. text is a JavaScript string of at most 100,000 ASCII code units and may contain letters, digits, whitespace, punctuation, or no alphanumeric characters. Compare only ASCII letters and digits, ignoring every other character and ignoring letter case. Return a Boolean, do not mutate or construct a fully cleaned copy of the input, and treat a string with no retained characters as a palindrome; non-string input is outside the contract. Examples: isPalindrome('A man, a plan, a canal: Panama') returns true, while isPalindrome('race a car') returns false. Target O(n) time and O(1) auxiliary space.

Short Interview Answer (30-60 seconds)

I would use two pointers, one from each end of the string. I move each pointer inward until it reaches an ASCII letter or digit. Then I compare those two characters after converting uppercase ASCII letters to lowercase. If they differ, I return false immediately. If they match, I move both pointers inward and continue. When the pointers meet or cross, I return true. This takes O(n) time and O(1) auxiliary space because I never build a cleaned copy.

Detailed Explanation

See the Code while reading this explanation.

The input is one JavaScript string with at most 100,000 ASCII code units. It can contain letters, digits, whitespace, punctuation, or no alphanumeric characters. We compare only ASCII letters and digits and ignore letter case. The function returns true when the retained characters read the same from both ends. It returns false when one compared pair differs. A string with no retained characters is a palindrome. Two pointers fit this problem because they compare both ends directly without constructing a cleaned copy.

Useful Questions to Ask the Interviewer
  1. Can I assume the input is always a JavaScript string, since non-string input is outside the contract?
  2. Should I compare only ASCII A-Z, a-z, and 0-9?
  3. Should a string with no retained ASCII letters or digits return true?
Determine whether a non-clean string is a palindrome. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is text, a JavaScript string of at most 100,000 ASCII code units. We retain only ASCII letters and digits. We ignore whitespace, punctuation, and every other character. We also ignore letter case. The output is a Boolean. If there are no retained characters, the answer is true.

2. Choose the two-pointer method

Use a left pointer and a right pointer. The left pointer starts at index 0. The right pointer starts at text.length - 1. Each pointer skips characters that are not ASCII letters or digits. When both pointers are on retained characters, compare them after ASCII case normalization. This avoids constructing a fully cleaned string.

3. Initialize and maintain the state

For the example A man, a plan, a canal: Panama, the string length is 30. So left = 0 and right = 29. The invariant is that every retained character outside the current pointer range has already been matched correctly with its partner from the other end. Characters that were skipped were not part of the comparison contract.

4. Walk through the example

At indices 0 and 29, A and a match after case normalization, so the pointers become 1 and 28. Index 1 is a space, so left becomes 2. At 2 and 28, m matches m. At 3 and 27, a matches a. At 4 and 26, n matches n. Left skips the comma at 5 and the space at 6. At 7 and 25, a matches a. Left skips the space at 8. At 9 and 24, p matches P after normalization. Right skips the space at 23 and the colon at 22. At 10 and 21, l matches l. At 11 and 20, a matches a. At 12 and 19, n matches n. Left skips the comma at 13 and the space at 14. At 15 and 18, a matches a. Left then skips the space at 16 and becomes 17. Now left = 17 and right = 17, so the pointers have met and the function returns true.

5. Explain why the result is correct

Before every comparison, both pointers are on retained ASCII letters or digits. Anything already outside the pointer range has either been ignored because it is not retained or has already matched correctly with its partner. Therefore, the first unequal retained pair proves the string is not a palindrome. If the pointers meet or cross without a mismatch, every required pair has matched, so the string is a palindrome.

6. Explain the JavaScript implementation

isAsciiAlnum checks the ASCII ranges for digits, uppercase letters, and lowercase letters. toLowerAscii converts uppercase ASCII letters to lowercase by adding 32 to their character code. The main function skips unwanted characters from both ends. It compares the normalized retained characters. A mismatch returns false immediately. A match moves both pointers inward. When the pointers meet or cross, the function returns true.

7. Explain complexity and edge cases

The time complexity is O(n) because the left pointer moves only right and the right pointer moves only left. The auxiliary space is O(1) because only a constant number of variables are used. Important cases are an empty string, a punctuation-only string such as !!!, a single retained character, mixed letter case, and a non-palindrome such as race a car.

Key Insight / Why This Solution Works

The key idea is to compare retained characters directly from both ends instead of creating a cleaned copy. Start left at the beginning and right at the end. Move left rightward while its character is not an ASCII letter or digit. Move right leftward for the same reason. When both pointers are valid, normalize uppercase ASCII letters to lowercase and compare their character codes. Return false on the first mismatch. Otherwise move both pointers inward. The invariant is that every retained character outside the current pointer range has already been matched correctly. When left >= right, no unmatched pair remains, so returning true is correct.

Code
function isPalindrome(text) {
  // Start one pointer at each end so retained characters can be compared in pairs.
  let left = 0;
  let right = text.length - 1;

  while (left < right) {
    // Skip non-alphanumeric ASCII characters from the left side.
    while (left < right && !isAsciiAlnum(text.charCodeAt(left))) {
      left++;
    }

    // Skip non-alphanumeric ASCII characters from the right side.
    while (left < right && !isAsciiAlnum(text.charCodeAt(right))) {
      right--;
    }

    // If skipping caused the pointers to meet or cross, every required pair matched.
    if (left >= right) {
      return true;
    }

    // Read the two retained ASCII character codes at the current pointers.
    const a = text.charCodeAt(left);
    const b = text.charCodeAt(right);

    // Normalize uppercase ASCII letters and stop immediately on a mismatch.
    if (toLowerAscii(a) !== toLowerAscii(b)) {
      return false;
    }

    // This pair matched, so move both pointers toward the center.
    left++;
    right--;
  }

  // The pointers met or crossed without any retained-character mismatch.
  return true;
}

function isAsciiAlnum(code) {
  // Keep ASCII digits 0-9, uppercase letters A-Z, and lowercase letters a-z.
  return (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
}

function toLowerAscii(code) {
  // Uppercase ASCII letters are 32 code values before their lowercase forms.
  return code >= 65 && code <= 90 ? code + 32 : code;
}

// Run the same verified example shown in the diagram.
console.log(isPalindrome('A man, a plan, a canal: Panama')); // true
Time & Space Complexity

Let n be text.length. The time complexity is O(n). The left pointer only moves right, and the right pointer only moves left, so the input is processed at most a constant number of times. The algorithm may also stop early if it finds a mismatch. Auxiliary space is O(1) because it stores only two pointers and a few character-code values. It does not create a cleaned string, array, map, set, or any other structure that grows with the input.

Where it is used

This two-pointer pattern is useful when software needs to compare values from opposite ends without copying the whole input. Similar ideas appear in text validation, symmetric-data checks, delimiter-aware parsing, and array problems where decisions depend on pairs near the left and right boundaries.

Why Interviewers Ask This

This problem tests whether a candidate recognizes the two-pointer pattern and can meet a constant-space requirement without building an unnecessary cleaned string. It also tests careful handling of ASCII filtering, case normalization, pointer movement, early failure, and inputs with no retained characters. For JavaScript, it checks precise use of character codes and equality. The interviewer also learns whether the candidate can maintain an invariant, reason about edge cases, and explain O(n) time and O(1) auxiliary space accurately.

Common interview mistakes

A common mistake is constructing a fully cleaned string first, which uses O(n) auxiliary space instead of the required O(1). Another mistake is accepting non-ASCII Unicode letters or digits even though the contract keeps only ASCII A-Z, a-z, and 0-9. Candidates can also forget to ignore case, move the wrong pointer while skipping punctuation, or forget to move both pointers after a successful comparison. Another mistake is returning false for an empty or punctuation-only string. With no retained characters, the pointers meet or cross without a mismatch, so the correct result is true.

Interview tip

State the invariant before coding: everything outside the current left and right pointers has already been either ignored because it is not retained or matched correctly with its opposite-side character. Then explain that both pointers move only inward. This makes the correctness argument and the O(n) time, O(1) auxiliary-space analysis easy to justify.

Interviewer may ask next
What would change if you were allowed to build a fully cleaned copy first?

I could first collect only ASCII letters and digits in normalized lowercase form, then compare that cleaned sequence from both ends. Correctness would stay the same because the cleaned sequence contains exactly the characters that matter. The time complexity would still be O(n), but auxiliary space would become O(n) because the cleaned copy grows with the input. The tradeoff is simpler comparison code in exchange for extra memory, so it would no longer meet this problem's O(1) auxiliary-space target.

What would change if the comparison had to support Unicode letters and digits instead of only ASCII?

The two-pointer idea could remain, but character handling would have to change. JavaScript strings use UTF-16 code units, and some Unicode characters use more than one code unit. I would need Unicode-aware code-point traversal, classification, and case handling instead of the fixed ASCII ranges and the + 32 conversion. Correctness would still depend on comparing the same logical retained characters from both ends. A suitable implementation can remain linear in the input size, but auxiliary space depends on whether Unicode normalization or temporary strings are required.

5. Find the length of the longest substring without repeating characters.CodingMediumMicrosoft

Question Details

Implement lengthOfLongestSubstring(text) in ECMAScript 2026. text is an ASCII string of length 0 through 100,000; repeated characters and the empty string are allowed, and inputs outside this schema need not be handled. Return the number of characters in the longest contiguous substring containing no repeated character. Do not return the substring, mutate the input, or enumerate every possible substring. Examples: lengthOfLongestSubstring('abcabcbb') returns 3, lengthOfLongestSubstring('bbbbb') returns 1, and lengthOfLongestSubstring('') returns 0. The required target is O(n) time with O(k) auxiliary space for the distinct characters tracked.

Short Interview Answer (30-60 seconds)

I would use a sliding window with two pointers, left and right, plus a Map called lastSeen. The Map stores each character and its most recent index. I move right through the string. If the current character was seen at or after left, I move left just past that earlier index. Then I update the Map and the best window length. The window always contains unique characters. This gives O(n) expected time and O(k) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

We need to find the length of the longest continuous part of the string that has no repeated character. We return only the length, not the substring. The input is an ASCII string with length from 0 to 100,000. It may be empty and may contain repeated characters. Instead of trying every possible substring, we keep one valid range and move through the string from left to right. A Map remembers the most recent position of each character, which lets us move the left side forward when a repeat appears.

Useful Questions to Ask the Interviewer
  1. Can I assume the input always follows the stated ASCII string contract?
  2. Should I return only the length and not the actual substring?
  3. Is O(n) expected time with O(k) auxiliary space the required target?
Find the length of the longest substring without repeating characters. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is one ASCII string called text. Its length can be from 0 to 100,000. We return the number of characters in the longest contiguous substring that contains no repeated character. We do not return the substring itself, and we do not change the input.

For the diagram example, text is "abcabcbb". The answer is 3. Valid substrings of length 3 include "abc", "bca", and "cab".

2. Choose the sliding window and Map

I use a sliding window with two boundaries. left is the first index of the current valid window. right is the index of the character currently being processed. The Map lastSeen stores each previously seen character and its most recent index.

The main invariant is that after handling the current character, the window from left through right contains no repeated character.

3. Initialize the state

Start with left = 0 and maxLen = 0. lastSeen starts as an empty Map. Then move right from index 0 through text.length - 1.

At each index, read c = text[right]. If c is already in lastSeen and its stored index is greater than or equal to left, the previous copy is inside the active window. Move left to lastSeen.get(c) + 1. Then store the current index for c.

4. Walk through the example

At right = 0, the character is "a". It has not been seen. The window is "a". Its length is 1, so maxLen becomes 1.

At right = 1, the character is "b". It has not been seen. The window becomes "ab". Its length is 2, so maxLen becomes 2.

At right = 2, the character is "c". It has not been seen. The window becomes "abc". Its length is 3, so maxLen becomes 3.

At right = 3, the character is "a". The previous "a" is at index

  1. Since 0 >= left, that earlier "a" is inside the active window. Move left from 0 to
  2. After storing the new index of "a", the valid window is "bca". Its length is 3.

At right = 4, the character is "b". Its previous index is

  1. Since 1 >= left, move left to
  2. The valid window is "cab". Its length is 3.

At right = 5, the character is "c". Its previous index is

  1. Since 2 >= left, move left to
  2. The valid window is "abc". Its length is 3.

At right = 6, the character is "b". Its previous index is

  1. Since 4 >= left, move left to
  2. The valid window becomes "cb". Its length is 2.

At right = 7, the character is "b" again. Its previous index is 6. Since 6 >= left, move left to 7. The valid window becomes "b". Its length is 1. maxLen stays 3.

5. Explain why the result is correct

When a repeated character appears inside the current window, left moves just past that character's previous occurrence. This removes the duplicate from the active window. Therefore, after each iteration, the current window contains unique characters. We measure every valid window ending at right, and maxLen keeps the largest valid length seen so far. For "abcabcbb", the maximum length is 3.

6. Explain the JavaScript implementation

The JavaScript code uses a Map from character to most recent index. For each right index, it checks whether the current character was previously seen inside the active window. If it was, left moves forward. The code then records the character's newest index, calculates the current window length as right - left + 1, and updates maxLen when this window is larger. After the loop finishes, it returns maxLen.

7. Explain complexity and edge cases

The expected time is O(n). The right pointer processes each character once. JavaScript Map lookup and insertion are O(1) on average. Auxiliary space is O(k), where k is the number of distinct characters tracked in lastSeen. An empty string returns

  1. A string such as "bbbbb" returns
  2. If every character is unique, the answer is the full string length.
Key Insight / Why This Solution Works

The key idea is to maintain one sliding window that contains only unique characters. right expands the window one character at a time. lastSeen maps each previously seen character to its most recent index. If the current character has a previous index greater than or equal to left, that previous copy is still inside the active window. We move left to one position after that index. Then we update lastSeen and calculate the current window length. The central invariant is that text[left..right] contains no repeated character after the duplicate check is handled.

Code
function lengthOfLongestSubstring(text) {
  // Map each previously seen character to its most recent index.
  const lastSeen = new Map();

  // left is the first index of the current duplicate-free window.
  let left = 0;

  // maxLen stores the largest valid window length found so far.
  let maxLen = 0;

  // Expand the window by moving right through the input once.
  for (let right = 0; right < text.length; right++) {
    // Read the character at the current right boundary.
    const c = text[right];

    // If the previous copy is still inside the active window,
    // move left just past that previous occurrence.
    if (lastSeen.has(c) && lastSeen.get(c) >= left) {
      left = lastSeen.get(c) + 1;
    }

    // Record the current index as the newest position of this character.
    lastSeen.set(c, right);

    // The current valid substring includes both left and right endpoints.
    const len = right - left + 1;

    // Keep the largest valid window length seen so far.
    if (len > maxLen) {
      maxLen = len;
    }
  }

  // For an empty string the loop does not run, so this correctly returns 0.
  return maxLen;
}

// Run the same verified example shown in the diagram.
console.log(lengthOfLongestSubstring('abcabcbb')); // 3
Time & Space Complexity

Expected time is O(n). The right pointer visits each position once. Each JavaScript Map lookup and insertion is O(1) on average, so the full algorithm takes O(n) expected time. Auxiliary space is O(k), where k is the number of distinct characters tracked in lastSeen. Because the input is ASCII, k is bounded by the ASCII character set, but the required complexity is still expressed as O(k) extra space.

Where it is used

This sliding-window pattern is useful when software needs to examine a continuous range while keeping a rule true inside that range. Examples include finding duplicate-free spans of text, analyzing recent event windows, and solving substring or subarray problems where the left boundary can move forward when the current range becomes invalid.

Why Interviewers Ask This

This problem tests whether you can recognize the sliding-window pattern, choose a suitable Map, and maintain a correct window invariant while handling repeated characters. It also checks pointer movement, off-by-one calculations, substring semantics, and duplicate handling. For JavaScript, it tests whether you can use Map correctly and explain its average lookup and insertion cost without claiming a stronger worst-case guarantee than the language provides.

Common interview mistakes

A common mistake is moving left backward when a character was seen earlier but is no longer inside the active window. The stored index must be checked with >= left before moving left. Another mistake is forgetting to update lastSeen with the current index. Some candidates calculate the window length before removing the duplicate, which can count an invalid window. Another error is treating a substring like a subsequence and allowing skipped characters. It is also incorrect to describe JavaScript Map operations as guaranteed O(1) worst-case operations.

Interview tip

State the invariant before writing the loop: after handling the current character, text[left..right] contains no duplicates. Then explain every left update using that rule. This makes the condition lastSeen.get(c) >= left easy to justify and helps avoid moving left backward.

Interviewer may ask next
How would you change the solution if you also had to return one longest substring instead of only its length?

Keep the same sliding window and lastSeen Map. Add a variable such as bestStart. Whenever the current window length becomes larger than maxLen, update both maxLen and bestStart = left. After the scan, return text.slice(bestStart, bestStart + maxLen). The expected time remains O(n), and the tracking Map still uses O(k) auxiliary space. Creating the returned substring also requires space proportional to the returned substring length.

How would the solution work if the characters arrived as a stream instead of one stored string?

The same sliding-window state can be updated as each character arrives. Keep the current index, left boundary, lastSeen Map, and maxLen. For each new character, check its previous index, move left when that previous copy is inside the active window, update lastSeen, and update maxLen. The expected processing time is O(n), and the auxiliary space is O(k). Returning only the length works naturally without storing the complete input.

6. Move all zeroes to the end of an array in place.CodingEasyMicrosoft

Question Details

Implement moveZeroes(values) in modern JavaScript. values is an owned array of 0 through 100,000 signed safe integers; duplicates, negative values, and any number of zeroes are allowed. Move every numeric 0 to the end while preserving the relative order of all nonzero values. Mutate the supplied array in place, allocate no second array proportional to the input, and leave its length unchanged; the return value is not assessed. Inputs outside this stated schema need not be supported. Example: after moveZeroes([0, 1, 0, 3, 12]), the array must be [1, 3, 12, 0, 0]; [] and [0, 0] remain valid. Use standard ECMAScript only and target O(n) time with O(1) auxiliary space.

Short Interview Answer (30-60 seconds)

I would use two pointers. The read pointer scans the array from left to right, and the write pointer marks the earliest position for the next nonzero value. When values[read] is not zero, I swap it with values[write] and move write forward. Zero values are skipped. This preserves the relative order of the nonzero values and leaves the zeroes at the end. The algorithm runs in O(n) time and uses O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The supplied array must be changed directly. Every numeric zero must move behind all nonzero values, while the nonzero values keep their original relative order. We cannot create another array whose size grows with the input. I use two positions called read and write. The read position checks each element from left to right. The write position marks where the next nonzero value belongs. When read finds a nonzero value, I swap it into the write position. This gives the required O(n) time and O(1) auxiliary space.

Useful Questions to Ask the Interviewer
  1. Should I mutate the supplied array instead of returning a new array? Yes. The stated contract requires in-place mutation.
  2. Must the relative order of the nonzero values stay unchanged? Yes.
  3. Is the function's return value important? No. Only the final contents of the supplied array are assessed.
Move all zeroes to the end of an array in place. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an owned JavaScript array containing 0 through 100,000 signed safe integers. Duplicates, negative values, and any number of zeroes are allowed. We must move every numeric 0 to the end. We must preserve the relative order of all nonzero values. The array length must stay unchanged, and we cannot allocate a second array proportional to the input.

For the diagram's example, the input is [0, 1, 0, 3, 12]. After the function runs, the same array must contain [1, 3, 12, 0, 0].

2. Choose the two-pointer method

I use two indices named read and write. read moves from left to right through the whole array. write marks the earliest position where the next nonzero value should be placed.

The central invariant is: before each new read step, every index before write contains the nonzero values already processed, in their original relative order. The processed positions from write through read - 1 contain zeroes.

If values[read] is 0, I skip it. If values[read] is nonzero, I swap values[read] with values[write], then increase write. Because read discovers nonzero values from left to right and write also moves only from left to right, the nonzero order is preserved.

3. Initialize the state

Set write = 0. The loop starts read at index 0. No nonzero values have been placed yet, so index 0 is the correct first write position.

4. Walk through the example

Start with [0, 1, 0, 3, 12], read = 0, and write = 0.

At read = 0, the current value is 0. The condition values[read] !== 0 is false. We skip it. The array stays [0, 1, 0, 3, 12], and write stays 0.

At read = 1, the current value is 1. It is nonzero. Swap positions write = 0 and read = 1. The array becomes [1, 0, 0, 3, 12]. Then write becomes 1.

At read = 2, the current value is 0. We skip it. The array stays [1, 0, 0, 3, 12], and write stays 1.

At read = 3, the current value is 3. It is nonzero. Swap positions 1 and 3. The array becomes [1, 3, 0, 0, 12]. Then write becomes 2.

At read = 4, the current value is 12. It is nonzero. Swap positions 2 and 4. The array becomes [1, 3, 12, 0, 0]. Then write becomes 3.

The loop ends when read reaches values.length. The final array is [1, 3, 12, 0, 0]. Its length is still 5, and the nonzero order 1, 3, 12 is preserved.

5. Explain why the result is correct

read processes the array from left to right. Each time it finds a nonzero value, that value is the next nonzero value from the original order. write points to the earliest place where that value belongs. Swapping the two positions extends the correct nonzero prefix by one. Any zero displaced by the swap stays behind that prefix. When the loop finishes, all nonzero values are at the front in the same relative order, and all remaining positions contain zeroes.

6. Explain the JavaScript implementation

The function keeps one variable, write, outside the loop. The for loop creates read and moves it from 0 through values.length - 1. The strict condition values[read] !== 0 checks whether the current value should be moved into the nonzero prefix. JavaScript destructuring assignment swaps values[write] and values[read] in place. After the swap, write increases by one. There is no explicit return because the supplied array itself is mutated and the return value is not assessed.

7. Explain complexity and edge cases

The read pointer visits every array position once, so the time complexity is O(n), where n is the array length. The algorithm uses only a constant number of index variables, so the auxiliary space complexity is O(1).

An empty array needs no work. An array containing only zeroes stays unchanged. An array containing no zeroes keeps the same order, although the implementation may perform harmless self-swaps. Duplicates and negative values also work because the algorithm only distinguishes numeric zero from nonzero values.

Key Insight / Why This Solution Works

The key idea is stable in-place compaction with two pointers. read discovers values from left to right. write marks the earliest position for the next nonzero value. The central invariant is that indices before write contain exactly the processed nonzero values in their original relative order, while processed positions from write through read - 1 contain zeroes. When read finds a nonzero value, swapping it with values[write] extends the correct nonzero prefix by one. This preserves order, mutates the original array, and needs no second array proportional to the input.

Code
function moveZeroes(values) {
  // write marks the earliest position where the next nonzero value belongs.
  let write = 0;

  // read scans every array position from left to right.
  for (let read = 0; read < values.length; read++) {
    // Zeroes stay behind the growing nonzero prefix, so only nonzero values move forward.
    if (values[read] !== 0) {
      // Put the current nonzero value into the next write position.
      // If read === write, this is a harmless self-swap.
      [values[write], values[read]] = [values[read], values[write]];

      // The correct nonzero prefix is now one element longer.
      write++;
    }
  }

  // No explicit return is required because values is mutated in place.
}

// Run the exact example shown in the diagram.
const values = [0, 1, 0, 3, 12];
moveZeroes(values);
console.log(values); // [1, 3, 12, 0, 0]
Time & Space Complexity

Let n be the number of elements in the array. The read pointer visits each of the n positions once, so the time complexity is O(n). The algorithm stores only a few index variables and performs swaps inside the supplied array. Its extra memory does not grow with n, so the auxiliary space complexity is O(1).

Where it is used

This two-pointer pattern is useful for stable in-place compaction. For example, software may need to move marker values behind useful values without creating another large buffer. The same read-and-write idea can also be used when compacting or filtering mutable arrays while preserving the order of the retained items.

Why Interviewers Ask This

This problem checks whether a candidate recognizes a simple two-pointer pattern and can mutate an array safely in place. It tests stable ordering, loop invariants, pointer movement, duplicates, negative values, zero handling, and empty inputs. It also checks whether the candidate can write clear modern JavaScript and explain why the implementation takes O(n) time and O(1) auxiliary space without using a second array proportional to the input.

Common interview mistakes

A common mistake is creating a second output array, which breaks the O(1) auxiliary-space requirement. Another is moving write when the current value is zero. That breaks the pointer invariant. A candidate can also accidentally change the relative order of the nonzero values by moving them in a different order. Using splice, repeated removal, or repeated shifting can make the solution slower than O(n). Another mistake is adding a separate zero-filling pass even though this swap-based version already leaves zeroes behind the nonzero prefix.

Interview tip

State the invariant before coding: write always marks the earliest position for the next nonzero value. Then trace one zero case and one nonzero case. This makes both pointer movement and the swap easy to justify.

Interviewer may ask next
Why does swapping values[read] with values[write] preserve the relative order of the nonzero values?

read discovers nonzero values strictly from left to right. Each discovered nonzero value is placed at the next write position, and write also moves only from left to right. The first nonzero discovered gets the first nonzero slot, the second gets the second slot, and so on. Therefore their relative order is preserved. The algorithm remains O(n) time and O(1) auxiliary space.

What happens if the array contains no zeroes or contains only zeroes?

If there are no zeroes, read and write move together. Each iteration may perform a harmless self-swap, so the array stays in the same order. If every value is zero, the condition is always false, write stays 0, and the array stays unchanged. Both cases still take O(n) time and O(1) auxiliary space.

7. Return any duplicate number from an array.CodingEasyMicrosoft

Question Details

Implement findAnyDuplicate(values) in modern JavaScript. values is an array of 2 through 100,000 safe integers, at least one value occurs more than once, and values may be negative; the function may return any repeated value when several exist. Do not mutate or sort the input, and inputs that violate the guarantee need not be supported. Example: findAnyDuplicate([7,2,5,2,9]) must return 2; for [4,1,4,1], either 4 or 1 is valid. Use standard ECMAScript only and target O(n) expected time with O(n) auxiliary space.

Short Interview Answer (30-60 seconds)

I would use a JavaScript Set called seen. I process the array from left to right. Before adding each value, I check whether it is already in the Set. If it is, that value appeared earlier, so I return it immediately. Otherwise, I add it to seen and continue. I process each item at most once and stop when the answer is found. This gives O(n) expected time and O(n) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

We need to return any number that appears more than once in the array. The array contains from 2 to 100,000 safe integers, and values may be negative. At least one repeated value is guaranteed. We must not sort or change the input. A simple solution is to remember values we have already visited. When we see a value that is already remembered, we return it immediately. A JavaScript Set is a good fit because membership checks and insertions are O(1) on average.

Useful Questions to Ask the Interviewer
  1. If several values are duplicated, may I return any repeated value?
  2. Can the array contain negative numbers?
  3. Should I keep the original array unchanged and avoid sorting it?
Return any duplicate number from an array. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an array named values. Its length is from 2 to 100,000. Every element is a JavaScript safe integer, so values may range from Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER. Negative values are allowed. At least one value appears more than once. We return a repeated value, not an index. If several duplicates exist, any one is valid. We must not mutate or sort the input.

2. Choose the algorithm and data structure

Use a Set named seen. It stores the distinct values that appeared at earlier positions. The central invariant is: before checking the current value, seen contains only values processed earlier. Therefore, if seen.has(x) is true, x has already appeared and is a valid duplicate.

3. Initialize and process from left to right

Start with seen as an empty Set. Process the array from left to right. For each value x, check seen.has(x) before inserting x. If the check is true, return x immediately. If the check is false, add x to seen and continue. If the loop somehow finishes, return null as a defensive fallback. The stated guarantee means that fallback is not expected to run.

4. Walk through the verified example

For values = [7, 2, 5, 2, 9], start with seen = {}. At index 0, x = 7. It is not in seen, so add 7. Now seen = {7}. At index 1, x = 2. It is not in seen, so add 2. Now seen = {7, 2}. At index 2, x = 5. It is not in seen, so add 5. Now seen = {7, 2, 5}. At index 3, x = 2. The Set already contains 2, so return 2 immediately. Index 4, which contains 9, is not processed. The diagram also shows [4, 1, 4, 1], where either 4 or 1 is a valid result because the problem allows any duplicate.

5. Explain why the result is correct

The Set contains only distinct values seen earlier in the traversal. When the current value is already in the Set, that same value must have appeared before. Therefore, returning it is correct. The problem guarantees that at least one duplicate exists, so the normal execution path finds a repeated value and stops.

6. Explain the JavaScript implementation, complexity, and edge cases

The implementation creates a Set, loops through values, checks seen.has(x), returns x when a repeat is found, and otherwise calls seen.add(x). JavaScript Set lookup and insertion are O(1) on average, so the total expected time is O(n). The Set may grow with the number of distinct values, so auxiliary space is O(n). The same approach works for negative safe integers, the minimum valid length such as [5, 5], large inputs up to the stated limit, and arrays with several different duplicated values.

Key Insight / Why This Solution Works

Use a Set to remember values that have already appeared. The key invariant is that, before checking the current value, seen contains the distinct values from earlier positions only. Check seen.has(x) before inserting x. If the value is present, it appeared earlier, so it is a valid duplicate and can be returned immediately. Otherwise, add it to seen. This keeps the input unchanged, avoids sorting, and matches the required O(n) expected-time approach.

Code
function findAnyDuplicate(values) {
  // Track each distinct value that has already been processed.
  const seen = new Set();

  // Process the input from left to right and stop when a duplicate is found.
  for (const x of values) {
    // Check before insertion so a true result means x appeared earlier.
    if (seen.has(x)) {
      // x is repeated, so return it immediately and skip all later values.
      return x;
    }

    // x has not appeared before, so remember it for future checks.
    seen.add(x);
  }

  // Defensive fallback; the stated problem guarantees a solution.
  return null;
}

// Run the same primary verified example shown in the diagram.
console.log(findAnyDuplicate([7, 2, 5, 2, 9])); // 2
Time & Space Complexity

Let n be values.length. We process the array at most once because the function can return as soon as it finds a duplicate. JavaScript Set lookup with has() and insertion with add() are O(1) on average, so the total expected time is O(n). This is an expected bound rather than a guaranteed worst-case O(n) bound. The Set may store up to O(n) distinct values, so the auxiliary space is O(n).

Where it is used

This pattern is useful when software needs to detect whether a value has appeared before while reading data in order. Examples include repeated IDs, duplicate imported records, duplicate event values, and repeated items in a stream that fits in memory. A Set is a good choice when we only need membership information and do not need an index or frequency count.

Why Interviewers Ask This

This problem checks whether you recognize the Set pattern for duplicate detection instead of using nested loops or sorting. It also tests whether you keep lookup and insertion in the correct order, return a value rather than an index, reason correctly about an early return, preserve the input, and explain JavaScript Set complexity accurately. The interviewer can also see whether you maintain a simple invariant and handle multiple valid duplicate results correctly.

Common interview mistakes
  1. Adding x to the Set before checking seen.has(x). That would make the current element appear to duplicate itself.
  2. Sorting the input. The question explicitly says not to sort or mutate the array.
  3. Returning an index instead of the repeated value. This problem asks for the number itself.
  4. Continuing to process later elements after finding a duplicate. The shown algorithm returns immediately, so 9 in the main example is not processed.
  5. Claiming guaranteed O(n) time. Because the solution depends on average O(1) Set operations, the correct wording is O(n) expected time.
Interview tip

State the invariant before writing the loop: seen contains the distinct values from earlier positions. Then emphasize that you check the Set before inserting the current value. That makes both the correctness and the early return easy to explain.

Interviewer may ask next
What changes if the input is not guaranteed to contain a duplicate?

The main Set algorithm stays the same. We still process values from left to right, check seen.has(x) before insertion, and return x when a duplicate is found. If the loop finishes without finding one, return a clear no-result value such as null. Correctness is preserved because every value was checked against all earlier distinct values. Expected time remains O(n), auxiliary space remains O(n), and the main tradeoff is that a no-duplicate input requires processing the whole array.

How would this solution work if the values arrived as a stream instead of one array?

Keep the same Set across incoming values. For each new value x, first check seen.has(x). If it is already present, report or return x as a duplicate. Otherwise, add x to seen and wait for the next value. The invariant stays the same: seen contains values received earlier. For m processed values, expected time is O(m) and auxiliary space is O(m) in the worst case. The tradeoff is memory because a long stream of unique values can make the Set keep growing.

8. Merge one linked list into another between two indexes.CodingMediumMicrosoft

Question Details

Implement mergeInBetween(list1, a, b, list2) in JavaScript. Each list is a non-cyclic singly linked list whose node shape is { val: number, next: ListNode | null }; list1 has at least three nodes, list2 has at least one, and 0 <= a <= b < length(list1). Remove the inclusive zero-based node range a..b from list1, connect the node before that range to the head of list2, connect the tail of list2 to the node after the removed range, and return the resulting head. Node links may be mutated, but no replacement nodes or value copies may be created; invalid inputs are outside the task. Example: merging [1000000,1000001,1000002] into [0,1,2,3,4,5] with a=3 and b=4 must produce [0,1,2,1000000,1000001,1000002,5]. Target O(n + m) time and O(1) auxiliary space.

Short Interview Answer (30-60 seconds)

I keep references to the important existing nodes instead of creating replacement nodes. I move curr to index a while before stays one node behind. Then I move after past index b, find the tail of list2, and reconnect the links. If a is zero, list2 becomes the new head. This preserves every retained node. The time complexity is O(n + m), and the auxiliary space is O(1).

Detailed Explanation

See the Code while reading this explanation.

We have two singly linked lists. We must remove the nodes from position a through b in list1, including both ends. Then we put the existing nodes of list2 into that gap. We must keep the original node objects and only change their next links. The main idea is to save the node before the removed range, the node after it, and the tail of list2. Then we reconnect those saved node references without losing the rest of the list.

Useful Questions to Ask the Interviewer
  1. Are a and b zero-based and inclusive? The problem says yes.
  2. May I mutate existing next links? The problem says yes, but I must not create replacement list nodes or copy node values into new nodes.
  3. If a is 0, should the head of list2 become the returned head? Yes.
Merge one linked list into another between two indexes. diagram
How to Explain It in an Interview
1. Understand the input and required output

list1 and list2 are non-cyclic singly linked lists. Each node has a numeric val and a next reference. We remove the inclusive range a..b from list1, insert the existing nodes of list2, and return the resulting head. For the example, list1 = [0,1,2,3,4,5], a = 3, b = 4, and list2 = [1000000,1000001,1000002]. The result is [0,1,2,1000000,1000001,1000002,5].

2. Keep the important node references

Start with before = null and curr = list1. Move curr forward until it reaches index a. Each time curr moves, set before to the node that curr is leaving. In the example, curr ends at the node with value 3, which is index 3, and before ends at the node with value 2, which is index 2. If a is 0, before remains null.

3. Find the node after the removed range

Set after = curr. The removed range contains b - a + 1 nodes. Here that is 4 - 3 + 1 = 2. Move after two links. It moves from value 3 to value 4, then from value 4 to value 5. Now after points to the first node that must remain after the removed range. If b is the final index, after becomes null.

4. Find the tail of list2 and rewire the links

Start tail at the head of list2 and follow next until tail.next is null. In the example, the tail is the node with value 1000002. Because before exists, set before.next = list2. This connects value 2 to value 1000000. Then set tail.next = after. This connects value 1000002 to value 5. The resulting chain is 0 -> 1 -> 2 -> 1000000 -> 1000001 -> 1000002 -> 5.

5. Explain why the result is correct

Before rewiring, before is either null when a = 0 or the existing node at index a - 1. after is the existing node at index b + 1, or null when b is the tail. These references are saved before links are changed. Connecting before to list2 when before exists, and connecting the tail of list2 to after, preserves every retained node and inserts every node of list2 exactly once.

6. Explain the JavaScript implementation

The first loop positions before and curr. The second loop moves after across the inclusive removed range. The while loop finds the last node of list2. If before exists, before.next becomes list2. The tail of list2 always points to after. Finally, the function returns list1 when the original head remains. If a is 0, it returns list2 because list2 is the new head.

7. Explain complexity and edge cases

Let n be the length of list1 and m be the length of list2. Traversing the required parts of list1 takes O(n) time in the worst case, and finding the tail of list2 takes O(m) time. Total time is O(n + m). The function stores only a constant number of node references, so auxiliary space is O(1). Important cases are a = 0, b = n - 1, a = b, and a one-node list2.

Key Insight / Why This Solution Works

The key idea is to save every boundary reference needed for the splice before changing any links. before is the existing node just before index a, unless a is zero. curr reaches the first node being removed. after is moved to the first node after index b. tail is the last node of list2. The central invariant is that these references identify existing nodes whose identities are preserved. Once the references are known, only the boundary links need to change, so no replacement nodes or copied values are needed.

Code
function mergeInBetween(list1, a, b, list2) {
  // before will point to the existing node immediately before index a.
  // It remains null when the removed range starts at the head.
  let before = null;
  let curr = list1;

  // Move curr to index a while before trails exactly one node behind.
  for (let i = 0; i < a; i++) {
    before = curr;
    curr = curr.next;
  }

  // Save the first node after the inclusive range a..b before rewiring.
  // Moving once for each removed node places after at index b + 1.
  let after = curr;
  for (let i = a; i <= b; i++) {
    after = after.next;
  }

  // Find the existing tail of list2 so it can be joined to after.
  let tail = list2;
  while (tail.next) {
    tail = tail.next;
  }

  // When a > 0, connect the retained prefix of list1 to list2.
  // When a === 0, there is no retained prefix and list2 becomes the new head.
  if (before) {
    before.next = list2;
  }

  // Connect list2 to the retained suffix of list1.
  // after may be null when b is the last index of list1.
  tail.next = after;

  // Keep the original head when a > 0. Otherwise return list2 as the new head.
  return before ? list1 : list2;
}

// Helper used only to build the diagram's example input.
function fromArray(values) {
  let head = null;
  let tail = null;

  for (const val of values) {
    const node = { val, next: null };
    if (head === null) {
      head = node;
      tail = node;
    } else {
      tail.next = node;
      tail = node;
    }
  }

  return head;
}

// Helper used only to display the resulting list as an array.
function toArray(head) {
  const values = [];
  for (let node = head; node !== null; node = node.next) {
    values.push(node.val);
  }
  return values;
}

// Run the exact example from the diagram.
const list1 = fromArray([0, 1, 2, 3, 4, 5]);
const list2 = fromArray([1000000, 1000001, 1000002]);
const result = mergeInBetween(list1, 3, 4, list2);

console.log(toArray(result));
// [0, 1, 2, 1000000, 1000001, 1000002, 5]
Time & Space Complexity

Let n be the number of nodes in list1 and m be the number of nodes in list2. Moving through list1 to locate the splice boundaries takes O(n) time in the worst case. Finding the last node of list2 takes O(m) time. Therefore total time is O(n + m). The algorithm stores only a constant number of references such as before, curr, after, and tail, plus loop counters. Their number does not grow with the input, so auxiliary space is O(1).

Where it is used

This pointer-splicing pattern is useful when an existing linked structure must be changed without copying all of its data. It can be used when inserting one linked sequence into another, replacing a range of linked nodes, or reconnecting parts of a mutable chain while keeping the original node objects.

Why Interviewers Ask This

This problem checks whether you can reason safely about mutable linked structures. The interviewer wants to see whether you save important references before changing links, handle an inclusive index range correctly, avoid losing the remaining list, and treat replacement of the head differently from a middle splice. It also tests whether your JavaScript implementation matches your pointer reasoning and whether you can explain the O(n + m) time and O(1) auxiliary space bounds accurately.

Common interview mistakes

A common mistake is changing links before saving the node after index b. That can lose the remaining suffix of list1. Another mistake is moving after the wrong number of times and forgetting that a..b is inclusive. Candidates may also forget the a = 0 case and try to use before.next when before is null. Another error is finding the wrong tail of list2. Creating replacement nodes or copying values inside mergeInBetween also violates the required node-identity constraint.

Interview tip

Before writing the rewiring code, say what each reference means: before is the node before the removed range, curr is the first removed node, after is the first retained node after the range, and tail is the last node of list2. Once those references are correct, the two boundary connections are easy to verify.

Interviewer may ask next
What changes if list2 is also given with a direct reference to its tail?

The splice itself does not change. We still find before and after in list1, connect before.next to list2 when before exists, and connect the supplied tail directly to after. We no longer need to walk through list2, so the time becomes O(n) instead of O(n + m). Auxiliary space remains O(1). Correctness is preserved because the same boundary references are connected. The tradeoff is that the caller must provide and maintain a correct tail reference.

What happens when the removed range starts at index 0?

before remains null because there is no node before the removed range. We still move after past index b and find the tail of list2. We skip before.next = list2, connect tail.next = after, and return list2 as the new head. The same boundary reasoning remains correct. The time complexity stays O(n + m), and the auxiliary space stays O(1).

9. Maintain weighted fruits in sorted order and query one fruit efficiently.CodingHardMicrosoft

Question Details

Implement class FruitIndex in JavaScript. The constructor receives an owned array of pairs [name, weight], where name is a non-empty string and weight is a finite number; duplicate names and duplicate weights are allowed. Maintain records in ascending weight order, preserving insertion order among equal weights. Provide add(name, weight), getByName(name), and all(): add inserts one record without sorting the entire collection again, getByName returns a new weight-sorted array of all pairs with the exact requested name, and all returns a defensive copy of every pair in global weight order. Do not mutate the constructor input or expose mutable internal arrays; malformed records are outside scope. Example: starting from [['apple',1],['orange',2],['pineapple',4]], then adding ['apple',3] and ['apple',4], getByName('apple') returns [['apple',1],['apple',3],['apple',4]], while all() remains globally sorted. State the time and retained-space costs of construction, insertion, and both queries.

Short Interview Answer (30-60 seconds)

I would keep two synchronized views of the records. A doubly linked list stores every fruit in ascending weight order, and an insertion counter keeps older records first when weights are equal. A Map stores each fruit name with a sorted array of references to the same nodes. Each add updates both structures without sorting the whole collection again. Construction is O(n^2), add is O(n + k), getByName is O(k), all is O(n), and retained internal space is O(n).

Detailed Explanation

See the Code while reading this explanation.

The class receives fruit names and weights. It must always keep all fruits ordered from the smallest weight to the largest. When two records have the same weight, the older record stays first. We can add new records without sorting everything again. We also need two safe queries. One returns all records for one exact fruit name in weight order. The other returns every record in global weight order. Neither query may expose the class's internal mutable arrays. The diagram solves this with one globally sorted doubly linked list and one Map containing sorted node references for each fruit name.

Useful Questions to Ask the Interviewer
  1. Should equal weights keep their original insertion order? Yes. The problem requires stable ordering.
  2. Can duplicate names and duplicate weights appear? Yes. Both are allowed.
  3. Must getByName and all return new arrays instead of internal storage? Yes. Internal mutable arrays must not be exposed.
Maintain weighted fruits in sorted order and query one fruit efficiently. diagram
How to Explain It in an Interview
1. Understand the input and required output

The constructor receives an array of [name, weight] pairs. Each name is a non-empty string, and each weight is a finite number. The constructor must not modify this input array. add(name, weight) inserts one new record. getByName(name) returns all exact-name matches in ascending weight order. all() returns every record in global ascending weight order. Both query methods return fresh arrays.

2. Choose the data structures and ordering rule

The first structure is a doubly linked list containing every record. It is sorted by (weight, order). The first value is the fruit weight. The second value is an increasing insertion number. This second value keeps equal weights stable because an older record has a smaller insertion number.

The second structure is a Map. Each key is a fruit name. Each value is an array of node references for that name. That array is also sorted by (weight, order). Both structures therefore use the same ordering rule.

3. Build the initial state

Start with an empty linked list, an empty Map, and orderCounter = 0. Process the constructor input in its original order. The example inserts ['apple',1], then ['orange',2], then ['pineapple',4]. Their insertion orders are 0, 1, and 2.

The initial global list is apple:1(0) -> orange:2(1) -> pineapple:4(2). The Map has apple -> [1], orange -> [2], and pineapple -> [4]. The constructor reads the supplied array but never changes it.

4. Walk through the example additions

For add('apple', 3), create a new node with order 3. Scan the global linked list until reaching the first node whose (weight, order) pair is greater. pineapple:4(2) is the first greater node, so insert apple:3(3) before it. The global list becomes apple:1(0) -> orange:2(1) -> apple:3(3) -> pineapple:4(2). Insert the same node reference into the apple array after weight 1, so the apple weights become [1,3].

For add('apple', 4), create a node with order 4. The existing pineapple:4(2) has the same weight but a smaller insertion order, so it stays first. No later global node exists, so append apple:4(4) at the tail. The final list is apple:1(0) -> orange:2(1) -> apple:3(3) -> pineapple:4(2) -> apple:4(4). The apple array becomes [1,3,4].

5. Run the two queries

getByName('apple') reads the already-sorted apple node array. It creates new pair arrays and returns [['apple',1],['apple',3],['apple',4]].

all() walks the linked list from head to tail. It creates new pair arrays and returns [['apple',1],['orange',2],['apple',3],['pineapple',4],['apple',4]].

Neither method returns the internal Map arrays or the internal linked-list nodes.

6. Explain why the result is correct

After every insertion, the linked list is sorted by (weight, order). A new node is inserted before the first node with a greater pair. This keeps smaller weights first and keeps older equal-weight records before newer ones.

Each fruit's Map array is maintained with the same rule. Therefore getByName is already in the required weight order. The linked list gives the required global order for all(). Both queries build new arrays, so callers cannot modify the class's internal structures.

7. Explain complexity and edge cases

Let n be the total number of records and k be the number of records for one fruit name. Construction is O(n^2) worst-case time because it performs repeated ordered insertions. One add takes O(n + k) worst-case time. getByName takes O(k), and all takes O(n). Retained internal space is O(n).

The same logic handles duplicate names, duplicate weights, negative weights, zero weights, finite decimal weights, and an empty initial collection.

Key Insight / Why This Solution Works

The key idea is to maintain two ordered views of the same Node objects. The doubly linked list gives the required global order. The Map gives direct access to all records for one exact fruit name. Both structures use the same ordering key: first weight, then the increasing insertion order. This is the central invariant. After every insertion, the global linked list is sorted by (weight, order), and every per-name array is also sorted by (weight, order). The insertion counter makes duplicate weights stable. This avoids sorting the entire collection again after every call to add.

Code
class Node {
  constructor(name, weight, order) {
    // Store the record values and the insertion number used for equal-weight ties.
    this.name = name;
    this.weight = weight;
    this.order = order;

    // These references place this node in the global doubly linked list.
    this.prev = null;
    this.next = null;
  }
}

class FruitIndex {
  constructor(pairs) {
    // The linked list contains every record in global (weight, order) order.
    this.head = null;
    this.tail = null;

    // Each fruit name maps to Node references sorted by the same ordering rule.
    this.map = new Map();

    // Increasing order values preserve insertion order when weights are equal.
    this.orderCounter = 0;

    // Read the constructor input in its original order without mutating it.
    for (const [name, weight] of pairs) {
      this._insert(name, weight);
    }
  }

  add(name, weight) {
    // Use the same ordered insertion logic for every new record.
    this._insert(name, weight);
  }

  _insert(name, weight) {
    // Give the new record the next insertion number.
    const node = new Node(name, weight, this.orderCounter++);

    // Find the first global node that should come after the new node.
    // Older equal-weight nodes have smaller order values, so we pass them.
    let cur = this.head;
    while (
      cur &&
      (cur.weight < node.weight || (cur.weight === node.weight && cur.order < node.order))
    ) {
      cur = cur.next;
    }

    if (!cur) {
      // No greater node exists, so the new node belongs at the tail.
      if (!this.head) {
        // The first record becomes both the head and the tail.
        this.head = this.tail = node;
      } else {
        // Append after the current tail and update both directions.
        node.prev = this.tail;
        this.tail.next = node;
        this.tail = node;
      }
    } else if (!cur.prev) {
      // cur is the head, so insert the new node before it.
      node.next = cur;
      cur.prev = node;
      this.head = node;
    } else {
      // Insert between cur.prev and cur without losing either neighbor.
      node.prev = cur.prev;
      node.next = cur;
      cur.prev.next = node;
      cur.prev = node;
    }

    // Get this name's sorted Node array, or start a new one.
    const arr = this.map.get(name) || [];

    // Find the new node's position using the same (weight, order) rule.
    let i = 0;
    while (
      i < arr.length &&
      (arr[i].weight < node.weight || (arr[i].weight === node.weight && arr[i].order < node.order))
    ) {
      i++;
    }

    // Insert only this Node reference. The per-name array remains sorted.
    arr.splice(i, 0, node);
    this.map.set(name, arr);
  }

  getByName(name) {
    // The stored array is already ordered for this exact fruit name.
    const arr = this.map.get(name) || [];

    // Return new pair arrays so internal mutable storage is never exposed.
    return arr.map((node) => [node.name, node.weight]);
  }

  all() {
    // Traverse the globally sorted list and build a fresh result array.
    const result = [];
    for (let cur = this.head; cur; cur = cur.next) {
      result.push([cur.name, cur.weight]);
    }

    // The result and every pair are defensive copies.
    return result;
  }
}

// Run the same example shown in the approved diagram.
const fruitIndex = new FruitIndex([
  ['apple', 1],
  ['orange', 2],
  ['pineapple', 4],
]);

fruitIndex.add('apple', 3);
fruitIndex.add('apple', 4);

console.log(fruitIndex.getByName('apple'));
// [['apple', 1], ['apple', 3], ['apple', 4]]

console.log(fruitIndex.all());
// [['apple', 1], ['orange', 2], ['apple', 3], ['pineapple', 4], ['apple', 4]]
Time & Space Complexity

Let n be the total number of stored records. Let k be the number of records for the relevant fruit name. Construction takes O(n^2) worst-case time because every constructor record is inserted in order, and each insertion may scan records already stored. One add takes O(n + k) worst-case time: up to O(n) to find the linked-list position and up to O(k) to find and insert into that fruit's array. Since k <= n, the total is also O(n) in big-O terms. getByName takes O(k) time because it creates one returned pair for each matching record. all takes O(n) time because it walks the full linked list. Retained internal space is O(n): one Node is stored for every record, and the per-name arrays together hold one Node reference for every record. The returned arrays use O(k) output space for getByName and O(n) output space for all, but those copies are not retained internal state.

Where it is used

This pattern is useful when software needs a globally ordered view and a grouped ordered view of the same changing records. For example, a frontend application could show one list sorted by priority while also supporting a filtered list for one category. Storing references to the same records lets both indexes stay synchronized without copying the full record objects into separate data sets.

Why Interviewers Ask This

This problem checks whether you can maintain two synchronized views of changing data. It tests stable ordering, duplicate names, duplicate weights, linked-list pointer updates, Map-based indexing, defensive copying, and JavaScript array behavior. It also tests whether you can describe an invariant clearly and connect the implementation to its real complexity, including the global O(n) scan and the O(k) shifting cost of inserting into a per-name array.

Common interview mistakes

A common mistake is sorting the full collection again inside every add, instead of inserting only the new record. Another mistake is comparing only weights and forgetting the insertion-order tie breaker, which can break stability for equal weights. A candidate may update the linked list but forget to update the per-name Map array, so the two views no longer describe the same records. Pointer rewiring can also disconnect part of the doubly linked list if prev or next references are changed incorrectly. Another mistake is returning the internal Map array directly instead of building a defensive copy. Finally, splice can shift O(k) array elements, so that cost must be included in insertion complexity.

Interview tip

State the invariant before writing the pointer code: the global linked list and every per-name array are both sorted by (weight, insertion order). Then show how one insertion preserves that rule in both places. This makes stable duplicate handling, query correctness, and the complexity explanation much easier to defend.

Interviewer may ask next
How would the design change if add had to be much faster for a very large collection?

The current design may scan O(n) global records and O(k) records for one name during an insertion. To reduce that cost, I would replace the linear ordered indexes with data structures that can locate insertion positions faster, such as balanced search trees keyed by (weight, order). The same ordering key preserves correctness and stable equal-weight behavior. With balanced ordered indexes, the global update can become O(log n), with a similar logarithmic update for the per-name index. Retained space stays O(n). The tradeoff is a more complex implementation, especially because JavaScript does not provide a standard built-in balanced search tree.

What would change if the class also needed remove(name, weight) for one matching record?

First, the contract must define which duplicate to remove. For example, it could remove the earliest inserted matching record. After finding that Node, I would unlink it from the doubly linked list by reconnecting its previous and next neighbors. I would also remove the same Node reference from that name's Map array. Updating both structures preserves the invariant. With the current design, finding the target and its array position can take O(n + k) time. The linked-list unlink is O(1) once the Node is known, while array removal can shift O(k) entries. Retained space remains O(n) overall.

10. Implement sequential auto-play for stories with per-story delays.CodingMediumMicrosoft

Question Details

Implement browser-JavaScript function async function autoStory(users). users is an array in display order; each entry is { stories }, and stories is an array of records { id: string, intervalInSec: number } in playback order. IDs are non-empty strings, delays are finite non-negative numbers, and malformed input is outside the task. For each user in order and then each of that user's stories in order, wait intervalInSec * 1000 milliseconds and call console.log(id) exactly once. No two waits or story logs may overlap, and the returned promise must fulfill with undefined only after the final story is logged. Use browser timers and promises only; do not use forEach with asynchronous callbacks or a third-party scheduler. Example: [{stories:[{id:'A',intervalInSec:0},{id:'B',intervalInSec:0}]},{stories:[{id:'C',intervalInSec:0}]}] must log A, then B, then C, and then fulfill.

Short Interview Answer (30-60 seconds)

I would process the users in display order and each user’s stories in playback order. For every story, I convert intervalInSec to milliseconds, await a Promise backed by setTimeout, and then log that story’s id exactly once. Because each await completes before the next loop iteration starts, waits and logs never overlap. After the final story is logged, the async function finishes and its Promise fulfills with undefined. The time complexity is O(T), with O(1) auxiliary space.

Detailed Explanation

See the Code while reading this explanation.

The goal is to play every story one after another. Users already have a display order, and each user’s stories already have a playback order. Before each story, we wait for that story’s delay. Then we print its id exactly once. We do not start the next wait until the current wait and log are complete. This preserves the required order and prevents overlap. Nested for...of loops with await match this behavior directly.

Useful Questions to Ask the Interviewer
  1. Should an empty users array simply finish immediately with undefined?
  2. If one user has no stories, should that user be skipped and processing continue with the next user?
  3. Should the delay always be interpreted in seconds and converted to milliseconds before calling the browser timer?
Implement sequential auto-play for stories with per-story delays. diagram
How to Explain It in an Interview
1. Understand the input and required output

The input is an array called users. Each user has a stories array. Each story has an id and intervalInSec. We process users from first to last. Inside each user, we process stories from first to last. Before logging a story id, we wait intervalInSec * 1000 milliseconds. Each id is logged exactly once. After all stories finish, the returned Promise fulfills with undefined.

2. Choose the sequential async approach

I use two nested for...of loops. The outer loop preserves user order. The inner loop preserves story order. For every story, I await one timer Promise before logging its id. The central invariant is that the current story’s wait and log both finish before the next story starts. Because of this, waits and logs never overlap.

3. Walk through the verified example

The first user has stories A and B. The second user has story C. All three intervalInSec values are 0. First, we wait 0 ms for A and then log A. Next, we wait 0 ms for B and then log B. After the first user is complete, we move to the second user. We wait 0 ms for C and then log C. The console order is A, then B, then C.

4. Explain the timer step

The code multiplies intervalInSec by 1000 because setTimeout takes milliseconds. The wait helper returns a Promise that resolves after the browser timer fires. Await pauses this async function until that Promise resolves. Only after the wait finishes does console.log(story.id) run. The loop then moves to the next story.

5. Explain why the result is correct

The nested loops preserve the exact required traversal order. The await inside the inner loop makes each story a complete sequential step: wait first, then log. The next story cannot begin before that step is finished. Therefore, there are no overlapping waits or logs. After C is logged in the example, both loops finish. The function has no explicit return value, so its Promise fulfills with undefined.

6. Explain the JavaScript implementation

The wait helper wraps setTimeout in a Promise. The outer for...of loop reads users in display order. The inner for...of loop reads stories in playback order. For each story, the code awaits wait(story.intervalInSec * 1000), then calls console.log(story.id). There is no forEach with an asynchronous callback, so the required sequential control flow is explicit.

7. Explain complexity and edge cases

Let T be the total number of stories across all users. Each story is processed once, so the algorithmic time complexity is O(T), apart from the real elapsed timer delays. Auxiliary space is O(1) because no data structure grows with the input. Empty users or empty story arrays produce no logs and finish with undefined. Zero delays still preserve order because every timer Promise is awaited. A single user or single story follows the same logic.

Key Insight / Why This Solution Works

The key idea is to make each story one complete sequential asynchronous step: wait, then log. Two nested for...of loops preserve the required traversal order. The outer loop processes users from first to last. The inner loop processes that user’s stories from first to last. For each story, a Promise-backed setTimeout is awaited before console.log runs. The central invariant is: before the next story begins, the current story’s wait and log are already complete. This invariant guarantees the required order and prevents overlapping waits or logs.

Code
async function autoStory(users) {
  // Create an awaitable browser-timer helper for one story delay.
  const wait = (ms) =>
    new Promise((resolve) => {
      // Resolve this Promise only after the requested delay has finished.
      setTimeout(resolve, ms);
    });

  // Visit users strictly in their display order.
  for (const user of users) {
    // Visit this user's stories strictly in playback order.
    for (const story of user.stories) {
      // Convert seconds to milliseconds and finish this wait before continuing.
      await wait(story.intervalInSec * 1000);

      // Log this story exactly once after its own delay completes.
      console.log(story.id);
    }
  }

  // Reaching the end of an async function fulfills its Promise with undefined.
}

// Run the exact example shown in the diagram.
const users = [
  {
    stories: [
      { id: 'A', intervalInSec: 0 },
      { id: 'B', intervalInSec: 0 },
    ],
  },
  {
    stories: [{ id: 'C', intervalInSec: 0 }],
  },
];

// Logs A, then B, then C. The returned Promise then fulfills with undefined.
autoStory(users);
Time & Space Complexity

Let T be the total number of stories across all users. The code handles each story exactly once, so its algorithmic time complexity is O(T). The real elapsed time also includes the requested timer delays. Auxiliary space is O(1). The function keeps only the current loop values and timer Promise. It does not create an array, map, queue, or other structure whose size grows with the number of stories.

Where it is used

This sequential async pattern is useful when browser actions must happen one at a time with a delay before each action. Examples include story auto-play, slide sequences, guided tutorials, timed UI steps, and other client-side flows where starting the next action before the current one finishes would be incorrect.

Why Interviewers Ask This

This problem checks whether you understand JavaScript asynchronous control flow rather than only Promise syntax. The interviewer wants to see whether you can preserve nested ordering, convert the delay correctly, await browser timers, and avoid async forEach behavior that creates unintended concurrency. It also tests whether you understand what an async function fulfills with, why sequential awaits prevent overlap, and how to state the time and auxiliary space complexity accurately.

Common interview mistakes

A common mistake is using forEach with an async callback. forEach does not wait for those callback Promises, so waits can overlap and the required order can break. Another mistake is logging before awaiting the delay. Starting every timer together with Promise.all is also wrong because the waits overlap. Candidates may also forget to multiply intervalInSec by 1000 before calling setTimeout. Finally, returning a non-undefined value would violate the required fulfillment result.

Interview tip

State the invariant before writing the code: “For each story, I finish its wait and log before starting the next story.” Then use nested for...of loops with await. That makes both the implementation and the correctness argument easy to follow.

Interviewer may ask next
Why should we not use forEach with an async callback here?

forEach does not wait for the Promise returned by an async callback. It can start multiple story callbacks before earlier ones finish, which violates the no-overlap requirement. Nested for...of loops work because await pauses the current async function until each story’s delay and log step is complete. The processing complexity remains O(T), and the auxiliary space for this sequential approach remains O(1).

What happens if the users array is empty or one user has no stories?

No special branch is required. If users is empty, the outer loop runs zero times and the async function fulfills with undefined. If a user has an empty stories array, the inner loop runs zero times for that user and processing continues with the next user. No extra ids are logged. The same algorithm still processes each existing story once, giving O(T) time and O(1) auxiliary space.

More questions load as you scroll

Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.

Company Notice: This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.