Master JavaScript with 25 timed, interview-style coding challenges covering string manipulation, array algorithms, object transformations, recursion, sorting, and searching.
Sharpen your JavaScript skills with 25 curated coding challenges simulating real technical interviews. Each problem includes a problem statement, constraints, an optimal solution, and expected output. Time yourself — aim for 5 min on Easy, 10 min on Medium, and 15–20 min on Hard.
Challenge 1: Reverse a String Without Built-in Methods
Problem: Reverse a string without using .reverse(), .split(), or any built-in reverse method.
Constraints: Input length 1–10,000 | O(n) time
function reverseString(str) {
let reversed = "";
for (let i = str.length - 1; i >= 0; i--) {
reversed += str[i];
}
return reversed;
}
console.log(reverseString("hello world"));
console.log(reverseString("JavaScript"));
Challenge 2: Count Vowels in a String
Problem: Count the number of vowels (a, e, i, o, u) in a string, case-insensitive.
Constraints: Any string input | O(n) time
function countVowels(str) {
const vowels = new Set(["a", "e", "i", "o", "u"]);
let count = 0;
for (const char of str.toLowerCase()) {
if (vowels.has(char)) count++;
}
return count;
}
console.log(countVowels("Hello World"));
console.log(countVowels("JavaScript Programming"));
console.log(countVowels("rhythm"));
Challenge 3: Find the Maximum Without Math.max
Problem: Find the largest number in an array without using Math.max().
Constraints: Non-empty array of integers | O(n) time, O(1) space
function findMax(arr) {
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] > max) max = arr[i];
}
return max;
}
console.log(findMax([3, 7, 2, 9, 1, 5]));
console.log(findMax([-10, -3, -7, -1]));
Challenge 4: Palindrome Checker
Problem: Check whether a string is a palindrome, ignoring spaces, punctuation, and case.
Constraints: Ignore non-alphanumeric chars | O(n) time
function isPalindrome(str) {
const cleaned = str.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;
}
console.log(isPalindrome("A man, a plan, a canal: Panama"));
console.log(isPalindrome("race a car"));
Challenge 5: Remove Duplicates Preserving Order
Problem: Remove duplicate values from an array while preserving first-occurrence order.
Constraints: O(n) time using Set
function removeDuplicates(arr) {
const seen = new Set();
const result = [];
for (const item of arr) {
if (!seen.has(item)) { seen.add(item); result.push(item); }
}
return result;
}
console.log(removeDuplicates([1, 2, 3, 2, 4, 1, 5, 3]));
console.log(removeDuplicates(["apple", "banana", "apple", "cherry"]));
[1, 2, 3, 4, 5]
["apple", "banana", "cherry"]
Challenge 6: FizzBuzz with Boom
Problem: Return an array from 1 to n: multiples of 3→"Fizz", 5→"Buzz", 7→"Boom", combine for shared multiples.
Constraints: 1 ≤ n ≤ 1000
function fizzBuzzBoom(n) {
const result = [];
for (let i = 1; i <= n; i++) {
let str = "";
if (i % 3 === 0) str += "Fizz";
if (i % 5 === 0) str += "Buzz";
if (i % 7 === 0) str += "Boom";
result.push(str || String(i));
}
return result;
}
const out = fizzBuzzBoom(21);
console.log(out[14]); // 15
console.log(out[20]); // 21
Challenge 7: Capitalize First Letter of Each Word
Problem: Capitalize the first letter of every word in a string.
Constraints: Words separated by spaces | Preserve other casing
function capitalizeWords(str) {
return str.split(" ").map(w => w.length ? w[0].toUpperCase() + w.slice(1) : w).join(" ");
}
console.log(capitalizeWords("hello world from javascript"));
console.log(capitalizeWords("the quick brown FOX"));
Hello World From Javascript
The Quick Brown FOX
Problem: Given an array of n numbers from 0 to n with one missing, find the missing number.
Constraints: O(n) time, O(1) space | Use sum formula
function findMissingNumber(arr) {
const n = arr.length;
const expectedSum = (n * (n + 1)) / 2;
const actualSum = arr.reduce((sum, num) => sum + num, 0);
return expectedSum - actualSum;
}
console.log(findMissingNumber([3, 0, 1]));
console.log(findMissingNumber([0, 1, 2, 4, 5, 6, 7, 8, 9]));
Challenge 9: Character Frequency Map
Problem: Return an object with character frequencies sorted by count descending (exclude spaces).
Constraints: Case-sensitive | O(n log n) due to sort
function charFrequency(str) {
const freq = {};
for (const char of str) {
if (char === " ") continue;
freq[char] = (freq[char] || 0) + 1;
}
return Object.fromEntries(Object.entries(freq).sort((a, b) => b[1] - a[1]));
}
console.log(charFrequency("hello world"));
console.log(charFrequency("aabbcc"));
{ l: 3, o: 2, h: 1, e: 1, w: 1, r: 1, d: 1 }
{ a: 2, b: 2, c: 2 }
Challenge 10: Array Chunking
Problem: Split an array into groups of a specified size.
Constraints: Last chunk may be smaller | Do not mutate input | O(n) time
function chunkArray(arr, size) {
const chunks = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
}
console.log(chunkArray([1, 2, 3, 4, 5, 6, 7], 3));
console.log(chunkArray([1, 2, 3, 4, 5], 10));
[[1, 2, 3], [4, 5, 6], [7]]
[[1, 2, 3, 4, 5]]
🟡 Medium (Challenges 11–20)
Challenge 11: Anagram Detection
Problem: Determine whether two strings are anagrams (same character frequencies, ignoring case/spaces).
Constraints: O(n) time, O(k) space
function areAnagrams(str1, str2) {
const normalize = s => s.toLowerCase().replace(/[^a-z0-9]/g, "");
const s1 = normalize(str1), s2 = normalize(str2);
if (s1.length !== s2.length) return false;
const count = {};
for (const c of s1) count[c] = (count[c] || 0) + 1;
for (const c of s2) { if (!count[c]) return false; count[c]--; }
return true;
}
console.log(areAnagrams("Listen", "Silent"));
console.log(areAnagrams("Dormitory", "Dirty room"));
console.log(areAnagrams("Hello", "World"));
Challenge 12: Flatten Nested Arrays (No .flat())
Problem: Flatten a deeply nested array into a single-level array without Array.flat().
Constraints: Arbitrary depth | Preserve order
function flattenArray(arr) {
const result = [];
const stack = [...arr];
while (stack.length) {
const item = stack.shift();
if (Array.isArray(item)) stack.unshift(...item);
else result.push(item);
}
return result;
}
console.log(flattenArray([1, [2, [3, [4, [5]]]]]));
console.log(flattenArray([[1, 2], [3, [4, 5]], [6]]));
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 6]
Challenge 13: Deep Object Clone
Problem: Create a deep clone handling nested objects, arrays, Dates, RegExp, and circular references.
Constraints: No JSON.parse/stringify | Handle circular refs via WeakMap
function deepClone(obj, seen = new WeakMap()) {
if (obj === null || typeof obj !== "object") return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
if (seen.has(obj)) return seen.get(obj);
const clone = Array.isArray(obj) ? [] : {};
seen.set(obj, clone);
for (const key of Object.keys(obj)) clone[key] = deepClone(obj[key], seen);
return clone;
}
const original = { name: "Test", nested: { a: 1, b: [2, 3] }, date: new Date("2026-06-12") };
const cloned = deepClone(original);
cloned.nested.a = 99;
console.log(original.nested.a);
console.log(cloned.nested.a);
console.log(cloned.date instanceof Date);
Challenge 14: Two Sum (Hash Map)
Problem: Return indices of two numbers in an array that add up to a target sum.
Constraints: Exactly one solution exists | O(n) time, O(n) space
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (map.has(complement)) return [map.get(complement), i];
map.set(nums[i], i);
}
return [];
}
console.log(twoSum([2, 7, 11, 15], 9));
console.log(twoSum([3, 2, 4], 6));
console.log(twoSum([1, 5, 8, 3, 9, 2], 7));
Challenge 15: Group Objects by Property
Problem: Group an array of objects by a specified property, supporting dot-notation for nested access.
Constraints: O(n) time | Handle missing properties
function groupBy(arr, key) {
return arr.reduce((groups, item) => {
const value = key.split(".").reduce((o, k) => o?.[k], item);
const groupKey = String(value ?? "undefined");
(groups[groupKey] ??= []).push(item);
return groups;
}, {});
}
const people = [
{ name: "Alice", dept: "Eng" }, { name: "Bob", dept: "Mkt" },
{ name: "Charlie", dept: "Eng" }, { name: "Diana", dept: "Mkt" }
];
const grouped = groupBy(people, "dept");
console.log(Object.keys(grouped));
console.log(grouped["Eng"].length);
Challenge 16: Implement Debounce
Problem: Write a debounce function that delays invoking a callback until after a wait period since the last call.
Constraints: Include .cancel() method | Preserve this context
function debounce(func, wait) {
let timeoutId = null;
function debounced(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), wait);
}
debounced.cancel = () => { clearTimeout(timeoutId); timeoutId = null; };
return debounced;
}
let callCount = 0;
const inc = debounce(() => { callCount++; }, 100);
inc(); inc(); inc();
console.log("Immediate calls:", callCount);
console.log("After timeout, count would be: 1");
Immediate calls: 0
After timeout, count would be: 1
Challenge 17: Matrix Spiral Traversal
Problem: Return all elements of an M×N matrix in clockwise spiral order from top-left.
Constraints: Handle non-square matrices | O(M×N) time
function spiralOrder(matrix) {
if (!matrix.length) return [];
const result = [];
let top = 0, bottom = matrix.length - 1, left = 0, right = matrix[0].length - 1;
while (top <= bottom && left <= right) {
for (let i = left; i <= right; i++) result.push(matrix[top][i]);
top++;
for (let i = top; i <= bottom; i++) result.push(matrix[i][right]);
right--;
if (top <= bottom) { for (let i = right; i >= left; i--) result.push(matrix[bottom][i]); bottom--; }
if (left <= right) { for (let i = bottom; i >= top; i--) result.push(matrix[i][left]); left++; }
}
return result;
}
console.log(spiralOrder([[1,2,3],[4,5,6],[7,8,9]]));
console.log(spiralOrder([[1,2,3,4],[5,6,7,8],[9,10,11,12]]));
[1, 2, 3, 6, 9, 8, 7, 4, 5]
[1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]
Challenge 18: Memoization with LRU Limit
Problem: Write a generic memoize function that caches results with an optional max cache size (LRU eviction).
Constraints: Handle multiple args via JSON key | Expose .cache property
function memoize(func, maxSize = Infinity) {
const cache = new Map();
function memoized(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
const val = cache.get(key);
cache.delete(key); cache.set(key, val); // refresh LRU
return val;
}
const result = func.apply(this, args);
cache.set(key, result);
if (cache.size > maxSize) cache.delete(cache.keys().next().value);
return result;
}
memoized.cache = cache;
return memoized;
}
let computeCount = 0;
const square = memoize((n) => { computeCount++; return n * n; });
console.log(square(4));
console.log(square(4));
console.log(square(5));
console.log("Computations:", computeCount);
Challenge 19: Longest Substring Without Repeating Characters
Problem: Find the length and value of the longest substring without repeating characters using sliding window.
Constraints: O(n) time | Two-pointer approach
function longestUniqueSubstring(s) {
const charIndex = new Map();
let maxLen = 0, maxStart = 0, start = 0;
for (let end = 0; end < s.length; end++) {
if (charIndex.has(s[end]) && charIndex.get(s[end]) >= start) {
start = charIndex.get(s[end]) + 1;
}
charIndex.set(s[end], end);
if (end - start + 1 > maxLen) { maxLen = end - start + 1; maxStart = start; }
}
return { length: maxLen, substring: s.slice(maxStart, maxStart + maxLen) };
}
console.log(longestUniqueSubstring("abcabcbb"));
console.log(longestUniqueSubstring("bbbbb"));
console.log(longestUniqueSubstring("pwwkew"));
{ length: 3, substring: "abc" }
{ length: 1, substring: "b" }
{ length: 3, substring: "wke" }
Challenge 20: Deep Object Diff
Problem: Compute the deep difference between two objects — additions, removals, and modifications.
Constraints: Recursive nested handling | Structured diff output
function deepDiff(obj1, obj2) {
const diff = {};
const allKeys = new Set([...Object.keys(obj1 || {}), ...Object.keys(obj2 || {})]);
for (const key of allKeys) {
const v1 = obj1?.[key], v2 = obj2?.[key];
if (!(key in (obj1 || {}))) { diff[key] = { type: "added", value: v2 }; }
else if (!(key in (obj2 || {}))) { diff[key] = { type: "removed", value: v1 }; }
else if (typeof v1 === "object" && v1 !== null && typeof v2 === "object" && v2 !== null && !Array.isArray(v1) && !Array.isArray(v2)) {
const nested = deepDiff(v1, v2);
if (Object.keys(nested).length) diff[key] = { type: "nested", changes: nested };
} else if (JSON.stringify(v1) !== JSON.stringify(v2)) {
diff[key] = { type: "modified", from: v1, to: v2 };
}
}
return diff;
}
const before = { name: "Alice", age: 30, address: { city: "NYC" } };
const after = { name: "Alice", age: 31, address: { city: "LA" }, role: "Eng" };
console.log(JSON.stringify(deepDiff(before, after), null, 2));
{
"age": { "type": "modified", "from": 30, "to": 31 },
"address": { "type": "nested", "changes": { "city": { "type": "modified", "from": "NYC", "to": "LA" } } },
"role": { "type": "added", "value": "Eng" }
}
🔴 Hard (Challenges 21–25)
Challenge 21: Merge Sort with Comparison Count
Problem: Implement merge sort from scratch. Return the sorted array and total comparison count.
Constraints: Stable sort | O(n log n) time, O(n) space
function mergeSort(arr) {
let comparisons = 0;
function merge(left, right) {
const result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
comparisons++;
if (left[i] <= right[j]) result.push(left[i++]);
else result.push(right[j++]);
}
return [...result, ...left.slice(i), ...right.slice(j)];
}
function sort(a) {
if (a.length <= 1) return a;
const mid = Math.floor(a.length / 2);
return merge(sort(a.slice(0, mid)), sort(a.slice(mid)));
}
const sorted = sort(arr);
return { sorted, comparisons };
}
console.log(mergeSort([38, 27, 43, 3, 9, 82, 10]));
console.log(mergeSort([5, 4, 3, 2, 1]));
{ sorted: [3, 9, 10, 27, 38, 43, 82], comparisons: 13 }
{ sorted: [1, 2, 3, 4, 5], comparisons: 8 }
Challenge 22: Binary Search Tree with Kth Smallest
Problem: Implement a BST with insert, search, delete, in-order traversal, and kth-smallest element.
Constraints: Delete handles all 3 cases | O(h) per operation
class TreeNode {
constructor(val) { this.val = val; this.left = this.right = null; }
}
class BST {
constructor() { this.root = null; }
insert(val) { this.root = this._ins(this.root, val); }
_ins(node, val) {
if (!node) return new TreeNode(val);
if (val < node.val) node.left = this._ins(node.left, val);
else if (val > node.val) node.right = this._ins(node.right, val);
return node;
}
search(val) {
let n = this.root;
while (n) { if (val === n.val) return true; n = val < n.val ? n.left : n.right; }
return false;
}
delete(val) { this.root = this._del(this.root, val); }
_del(node, val) {
if (!node) return null;
if (val < node.val) node.left = this._del(node.left, val);
else if (val > node.val) node.right = this._del(node.right, val);
else {
if (!node.left) return node.right;
if (!node.right) return node.left;
let succ = node.right;
while (succ.left) succ = succ.left;
node.val = succ.val;
node.right = this._del(node.right, succ.val);
}
return node;
}
inOrder() {
const res = [];
(function traverse(n) { if (!n) return; traverse(n.left); res.push(n.val); traverse(n.right); })(this.root);
return res;
}
kthSmallest(k) {
let count = 0, result = null;
(function traverse(n) {
if (!n || result !== null) return;
traverse(n.left);
if (++count === k) { result = n.val; return; }
traverse(n.right);
})(this.root);
return result;
}
}
const bst = new BST();
[8, 3, 10, 1, 6, 14, 4, 7, 13].forEach(v => bst.insert(v));
console.log("In-order:", bst.inOrder());
console.log("Search 6:", bst.search(6));
console.log("3rd smallest:", bst.kthSmallest(3));
bst.delete(3);
console.log("After delete 3:", bst.inOrder());
In-order: [1, 3, 4, 6, 7, 8, 10, 13, 14]
Search 6: true
3rd smallest: 4
After delete 3: [1, 4, 6, 7, 8, 10, 13, 14]
Challenge 23: Dynamic Programming — Coin Change
Problem: Find the minimum number of coins to make an amount, and return which coins are used.
Constraints: Unlimited coin supply | O(amount × coins) time | Track coin selection
function coinChange(coins, amount) {
const dp = new Array(amount + 1).fill(Infinity);
const coinUsed = new Array(amount + 1).fill(-1);
dp[0] = 0;
for (let i = 1; i <= amount; i++) {
for (const coin of coins) {
if (coin <= i && dp[i - coin] + 1 < dp[i]) {
dp[i] = dp[i - coin] + 1;
coinUsed[i] = coin;
}
}
}
if (dp[amount] === Infinity) return { minCoins: -1, combination: [] };
const combination = [];
let rem = amount;
while (rem > 0) { combination.push(coinUsed[rem]); rem -= coinUsed[rem]; }
return { minCoins: dp[amount], combination: combination.sort((a, b) => b - a) };
}
console.log(coinChange([1, 5, 10, 25], 67));
console.log(coinChange([2, 5, 10], 3));
console.log(coinChange([1, 3, 4], 6));
{ minCoins: 6, combination: [25, 25, 10, 5, 1, 1] }
{ minCoins: -1, combination: [] }
{ minCoins: 2, combination: [3, 3] }
Challenge 24: Unique Permutations (Backtracking)
Problem: Generate all unique permutations of an array with possible duplicates using backtracking.
Constraints: No brute force + filter | Lexicographic order | O(n! × n)
function uniquePermutations(arr) {
const results = [];
const sorted = [...arr].sort((a, b) => a - b);
const used = new Array(sorted.length).fill(false);
function backtrack(current) {
if (current.length === sorted.length) { results.push([...current]); return; }
for (let i = 0; i < sorted.length; i++) {
if (used[i]) continue;
if (i > 0 && sorted[i] === sorted[i - 1] && !used[i - 1]) continue;
used[i] = true;
current.push(sorted[i]);
backtrack(current);
current.pop();
used[i] = false;
}
}
backtrack([]);
return results;
}
console.log(uniquePermutations([1, 1, 2]));
console.log("Count [1,2,3]:", uniquePermutations([1, 2, 3]).length);
console.log("Count [1,1,1,2]:", uniquePermutations([1, 1, 1, 2]).length);
[[1, 1, 2], [1, 2, 1], [2, 1, 1]]
Count [1,2,3]: 6
Count [1,1,1,2]: 4
Challenge 25: LRU Cache (O(1) Operations)
Problem: Implement an LRU Cache with O(1) get and put using a doubly-linked list + hash map.
Constraints: Evict least recently used on capacity overflow | Track hit/miss stats
class LRUNode {
constructor(key, value) { this.key = key; this.value = value; this.prev = this.next = null; }
}
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.map = new Map();
this.head = new LRUNode(null, null);
this.tail = new LRUNode(null, null);
this.head.next = this.tail;
this.tail.prev = this.head;
this.hits = 0; this.misses = 0;
}
_remove(node) { node.prev.next = node.next; node.next.prev = node.prev; }
_addFront(node) {
node.next = this.head.next; node.prev = this.head;
this.head.next.prev = node; this.head.next = node;
}
get(key) {
if (this.map.has(key)) {
this.hits++;
const node = this.map.get(key);
this._remove(node); this._addFront(node);
return node.value;
}
this.misses++;
return -1;
}
put(key, value) {
if (this.map.has(key)) {
const node = this.map.get(key);
node.value = value;
this._remove(node); this._addFront(node);
} else {
const node = new LRUNode(key, value);
this.map.set(key, node); this._addFront(node);
if (this.map.size > this.capacity) {
const lru = this.tail.prev;
this._remove(lru); this.map.delete(lru.key);
}
}
}
getStats() {
const total = this.hits + this.misses;
return { size: this.map.size, hits: this.hits, misses: this.misses,
hitRate: total ? ((this.hits / total) * 100).toFixed(1) + "%" : "0%" };
}
}
const cache = new LRUCache(3);
cache.put("a", 1); cache.put("b", 2); cache.put("c", 3);
console.log("Get a:", cache.get("a"));
cache.put("d", 4); // evicts "b"
console.log("Get b:", cache.get("b"));
console.log("Get c:", cache.get("c"));
cache.put("e", 5); // evicts "d"
console.log("Get d:", cache.get("d"));
console.log("Stats:", cache.getStats());
Get a: 1
Get b: -1
Get c: 3
Get d: -1
Stats: { size: 3, hits: 2, misses: 2, hitRate: "50.0%" }
📊 Challenge Summary
| Difficulty | Count | Topics | Target Time |
|---|
| 🟢 Easy | 10 | Strings, arrays, math, basic logic | 5 min |
| 🟡 Medium | 10 | Hash maps, recursion, sliding window, objects | 10 min |
| 🔴 Hard | 5 | Sorting, trees, DP, backtracking, cache design | 15–20 min |
🎯 Interview Tips
- Clarify first — Ask about edge cases and constraints before coding.
- Think aloud — Explain your approach; interviewers evaluate your process.
- Brute force then optimize — Get a working solution, then improve it.
- State complexity — Always mention time and space complexity.
- Test mentally — Walk through your code with examples before submitting.
*Last updated: June 12, 2026 — Solutions tested with Node.js 22+ and modern JavaScript engines.*