Skip to content

Real Interview Questions

ASKED IN REAL INTERVIEWS

A running collection of questions asked in actual interviews — not curated LeetCode picks. Every solution below is verified against real inputs before being written here. New questions get added to the bottom as they come in.

Given an amount in cents and a set of coin denominations, return how many of each denomination to use so the total number of coins is as small as possible.

Example: 33 cents with denominations [1, 5, 10, 25] → 3 pennies + 1 nickel + 0 dimes + 1 quarter (5 coins total).

function makeChange(amount, coins = [25, 10, 5, 1]) {
const result = {};
let remaining = amount;
for (const c of coins) {
result[c] = Math.floor(remaining / c);
remaining %= c;
}
return result;
}
makeChange(33); // { 25: 1, 10: 0, 5: 1, 1: 3 }

Take the largest coin first, use as many of it as fit, then move to the next-smaller coin with whatever remains. This greedy approach only gives the true minimum for a “canonical” coin system like [1, 5, 10, 25] — coins where each one divides evenly into the value above it. For an arbitrary set of denominations (say [1, 3, 4] for an amount of 6), greedy can be wrong: it would pick 4 + 1 + 1 (3 coins) instead of the actual best 3 + 3 (2 coins). An arbitrary coin set needs a DP approach instead (same shape as LeetCode’s Coin Change).

Complexity: O(coins.length) time, O(coins.length) space — one pass, no recursion, for a canonical coin system.

A tree where each node can have any number of children (not just two), and every node holds a point value. Find the highest-scoring path from the root down to any leaf.

1
/ \
/ \
/ \
1 3
/ \ \
5 10 7

The best path is 1 → 1 → 10 = 12, beating 1 → 1 → 5 = 7 and 1 → 3 → 7 = 11.

function findMaxPoints(node) {
if (!node.children || node.children.length === 0) return node.value;
let best = -Infinity;
for (const child of node.children) {
best = Math.max(best, findMaxPoints(child));
}
return node.value + best;
}
const game = {
value: 1,
children: [
{ value: 1, children: [{ value: 5, children: [] }, { value: 10, children: [] }] },
{ value: 3, children: [{ value: 7, children: [] }] },
],
};
findMaxPoints(game); // 12

At each node, recurse into every child, take whichever child returned the largest sum, and add the current node’s own value on top. A leaf (no children) just returns its own value — that’s the base case the recursion bottoms out on.

Complexity: O(n) time (every node visited once), O(h) space for the recursion stack, where h is the tree’s height.

3. Generate all valid combinations of parentheses

Section titled “3. Generate all valid combinations of parentheses”

Already built out as a full Problem Bank page with brute-force and optimal animated solutions — see Generate Parentheses.

Given a sorted array of unique numbers drawn from some range starting at 0, return every number in that range that’s missing from the array.

Example: [0, 1, 2, 5, 7, 8] → missing 3, 4, 6.

function findMissing(nums) {
const present = new Set(nums);
const max = Math.max(...nums);
const missing = [];
for (let i = 0; i <= max; i++) {
if (!present.has(i)) missing.push(i);
}
return missing;
}
findMissing([0, 1, 2, 5, 7, 8]); // [3, 4, 6]

Put every number that IS present into a Set for O(1) lookups, then walk the full range from 0 up to the largest number actually in the array, collecting whichever ones the Set doesn’t have.

Complexity: O(n + max) time, O(n) space, where max is the largest value in the input.

Return whether a string reads the same forwards and backwards, ignoring case, spaces, and punctuation.

function isPalindrome(s) {
const cleaned = s.toLowerCase().replace(/[^a-z0-9]/g, '');
let left = 0, right = cleaned.length - 1;
while (left < right) {
if (cleaned[left] !== cleaned[right]) return false;
left++;
right--;
}
return true;
}
isPalindrome('racecar'); // true
isPalindrome('A man, a plan, a canal: Panama'); // true
isPalindrome('hello'); // false

Strip everything except letters and digits first, then converge two pointers from both ends toward the middle — any mismatched pair means it isn’t a palindrome.

Complexity: O(n) time, O(n) space for the cleaned copy (O(1) extra if you skip non-alphanumeric characters in place instead of pre-cleaning the string).

An isogram is a word with no repeating letters. Return whether a given word qualifies.

function isIsogram(s) {
const letters = s.toLowerCase().replace(/[^a-z]/g, '');
return new Set(letters).size === letters.length;
}
isIsogram('subdermatoglyphic'); // true
isIsogram('hello'); // false — repeats "l"

A Set can never hold duplicate values — so if converting the letters to a Set shrinks the count, some letter had to have repeated.

Complexity: O(n) time, O(n) space.

7. Longest common substring between two strings

Section titled “7. Longest common substring between two strings”

Given two strings, find the longest run of characters that appears, contiguously and in the same order, in both.

Example: "Pakistan" and "sweepstakes""sta" (present in both: “pakistan” and “sweepstakes”).

function longestCommonSubstring(a, b) {
const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
let maxLen = 0, endIndex = 0;
for (let i = 1; i <= a.length; i++) {
for (let j = 1; j <= b.length; j++) {
if (a[i - 1] === b[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
if (dp[i][j] > maxLen) {
maxLen = dp[i][j];
endIndex = i;
}
}
}
}
return a.slice(endIndex - maxLen, endIndex);
}
longestCommonSubstring('pakistan', 'sweepstakes'); // "sta"

dp[i][j] tracks the length of a common substring ending exactly at a[i-1] and b[j-1]. Every time both characters match, that length is one more than whatever streak ended right before it (dp[i-1][j-1]) — a single mismatch resets the streak to 0, since the substring must be contiguous. Track the longest streak seen and where it ended to slice it back out at the end.

Complexity: O(a.length × b.length) time and space for the DP table.

Move every zero in an array to the end, keeping the relative order of the non-zero elements — without allocating a second array.

function moveZeroesRight(arr) {
let insertPos = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== 0) {
[arr[insertPos], arr[i]] = [arr[i], arr[insertPos]];
insertPos++;
}
}
return arr;
}
moveZeroesRight([0, 1, 0, 3, 12]); // [1, 3, 12, 0, 0]

insertPos tracks the next open slot for a non-zero value. Walk the array once; every time a non-zero is found, swap it into that slot and advance insertPos. Everything left behind as the scan passes over it is either a zero or has already been swapped forward — by the end, every non-zero has been pulled to the front in its original relative order, and every zero has been pushed to the back.

Complexity: O(n) time, O(1) extra space — the swap happens in the same array, no second array or temp buffer.

9. Reconstruct an array from adjacent pairs

Section titled “9. Reconstruct an array from adjacent pairs”

An array of n unique numbers has been lost, but every adjacent pair from it survived, in any order and possibly flipped. Reconstruct one valid version of the original array.

Example: adjacentPairs = [[4,-2], [1,4], [-3,1]][-2, 4, 1, -3] (or its reverse — both count as correct).

function restoreArray(adjacentPairs) {
const graph = new Map();
for (const [u, v] of adjacentPairs) {
if (!graph.has(u)) graph.set(u, []);
if (!graph.has(v)) graph.set(v, []);
graph.get(u).push(v);
graph.get(v).push(u);
}
let start;
for (const [node, neighbors] of graph) {
if (neighbors.length === 1) {
start = node;
break;
}
}
const result = [start];
const seen = new Set([start]);
while (result.length < graph.size) {
const last = result[result.length - 1];
const next = graph.get(last).find((candidate) => !seen.has(candidate));
result.push(next);
seen.add(next);
}
return result;
}
restoreArray([[4, -2], [1, 4], [-3, 1]]); // [-2, 4, 1, -3]

Every number in the original array is adjacent to at most 2 others (its left and right neighbors) — except the two numbers at the very ends of the array, which are adjacent to only

  1. Build an adjacency map from the pairs, find one of those two endpoints (the only nodes with exactly 1 neighbor), then walk the chain: at each step, move to whichever neighbor hasn’t been visited yet. That walk reconstructs the array in order.

Complexity: O(n) time and space — building the graph and walking it are each a single pass.

Given a string, return the character(s) with the second-highest number of occurrences.

Example: "AAAABBBCCD" → counts are A: 4, B: 3, C: 2, D: 1 → second-highest count is 3, held by "B".

function secondHighestFreqChar(s) {
const counts = {};
for (const ch of s) counts[ch] = (counts[ch] || 0) + 1;
const uniqueCounts = [...new Set(Object.values(counts))].sort((a, b) => b - a);
const secondCount = uniqueCounts[1];
return Object.keys(counts).filter((ch) => counts[ch] === secondCount);
}
secondHighestFreqChar('AAAABBBCCD'); // ["B"]

Count every character first, then de-duplicate the counts themselves into a Set — this is what correctly handles a tie for first place (two characters sharing the highest count) without both of them wrongly showing up as “second highest.” Sort the distinct counts descending and take the one at index 1; any character whose count matches that value is a correct answer (there can be more than one, e.g. a three-way tie).

Complexity: O(n + k log k) time, where k is the number of distinct characters — one pass to count, then a sort over the (small, bounded) set of distinct counts.