15 JavaScript Algorithm Interview Questions and Answers
Prepare for your next tech interview with our guide on JavaScript algorithms, featuring curated questions to enhance your problem-solving skills.
Prepare for your next tech interview with our guide on JavaScript algorithms, featuring curated questions to enhance your problem-solving skills.
JavaScript is a cornerstone of modern web development, enabling dynamic and interactive user experiences. Its versatility extends beyond the browser, finding applications in server-side development with Node.js, mobile app development, and even desktop applications. Mastery of JavaScript algorithms is crucial for optimizing performance and solving complex problems efficiently, making it a highly sought-after skill in the tech industry.
This article offers a curated selection of JavaScript algorithm questions designed to challenge and refine your problem-solving abilities. By working through these examples, you’ll gain a deeper understanding of algorithmic concepts and be better prepared to tackle technical interviews with confidence.
To find the second largest number in an array, iterate through the array while keeping track of the largest and second largest numbers. This ensures efficient traversal.
Example:
function findSecondLargest(arr) { let first = -Infinity, second = -Infinity; for (let num of arr) { if (num > first) { second = first; first = num; } else if (num > second && num < first) { second = num; } } return second; } console.log(findSecondLargest([10, 5, 8, 12, 7])); // Output: 10 console.log(findSecondLargest([4, 4, 4, 4, 4])); // Output: -Infinity (or handle as needed)
A palindrome reads the same forward and backward, ignoring spaces, punctuation, and capitalization. To check if a string is a palindrome:
Example:
function isPalindrome(str) { const normalizedStr = str.replace(/[^A-Za-z0-9]/g, '').toLowerCase(); return normalizedStr === normalizedStr.split('').reverse().join(''); } // Example usage console.log(isPalindrome("A man, a plan, a canal, Panama")); // true console.log(isPalindrome("race a car")); // false
Recursion involves a function calling itself to solve a problem. Calculating the factorial of a number is a classic example. The factorial of n (n!) is the product of all positive integers up to n. Define a base case for n = 0 or 1, where the factorial is 1, and a recursive case for n * factorial(n-1).
Example:
function factorial(n) { if (n === 0 || n === 1) { return 1; } return n * factorial(n - 1); } console.log(factorial(5)); // Output: 120
Bubble sort repeatedly steps through the list, comparing and swapping adjacent items if needed, until the list is sorted.
Example:
function bubbleSort(arr) { let n = arr.length; for (let i = 0; i < n - 1; i++) { for (let j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { let temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } return arr; } console.log(bubbleSort([64, 34, 25, 12, 22, 11, 90])); // Output: [11, 12, 22, 25, 34, 64, 90]
Binary search efficiently finds the index of a target value in a sorted array by repeatedly dividing the search interval in half.
Example:
function binarySearch(arr, target) { let left = 0; let right = arr.length - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); if (arr[mid] === target) { return mid; } else if (arr[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1; // Target not found } const sortedArray = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const targetValue = 5; console.log(binarySearch(sortedArray, targetValue)); // Output: 4
The longest increasing subsequence (LIS) in an array is a subsequence of elements in increasing order. Use dynamic programming to find the LIS length by maintaining an array dp
where dp[i]
represents the LIS length ending at index i
.
function lengthOfLIS(nums) { if (nums.length === 0) return 0; let dp = new Array(nums.length).fill(1); for (let i = 1; i < nums.length; i++) { for (let j = 0; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } } return Math.max(...dp); } // Example usage: console.log(lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18])); // Output: 4
In-order traversal of a binary tree visits nodes in the order: left subtree, root node, right subtree. This retrieves binary search tree values in ascending order.
Example:
class TreeNode { constructor(value) { this.value = value; this.left = null; this.right = null; } } function inOrderTraversal(node, result = []) { if (node !== null) { inOrderTraversal(node.left, result); result.push(node.value); inOrderTraversal(node.right, result); } return result; } // Example usage: const root = new TreeNode(1); root.left = new TreeNode(2); root.right = new TreeNode(3); root.left.left = new TreeNode(4); root.left.right = new TreeNode(5); console.log(inOrderTraversal(root)); // Output: [4, 2, 5, 1, 3]
Memoization optimizes recursive algorithms by storing and reusing results of expensive function calls. This is useful for calculating the nth Fibonacci number, as the naive recursive approach has exponential time complexity.
Example:
function fibonacci(n, memo = {}) { if (n <= 1) return n; if (memo[n]) return memo[n]; memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo); return memo[n]; } console.log(fibonacci(10)); // Output: 55 console.log(fibonacci(50)); // Output: 12586269025
The N-Queens problem can be solved using backtracking. Place queens one by one in different columns, checking for conflicts with already placed queens. If a conflict is found, backtrack and try the next position.
function solveNQueens(n) { const solutions = []; const board = Array.from({ length: n }, () => Array(n).fill('.')); function isSafe(row, col) { for (let i = 0; i < col; i++) { if (board[row][i] === 'Q') return false; } for (let i = row, j = col; i >= 0 && j >= 0; i--, j--) { if (board[i][j] === 'Q') return false; } for (let i = row, j = col; i < n && j >= 0; i++, j--) { if (board[i][j] === 'Q') return false; } return true; } function solve(col) { if (col === n) { solutions.push(board.map(row => row.join(''))); return; } for (let i = 0; i < n; i++) { if (isSafe(i, col)) { board[i][col] = 'Q'; solve(col + 1); board[i][col] = '.'; } } } solve(0); return solutions; } console.log(solveNQueens(4));
The KMP algorithm consists of preprocessing the pattern to create the longest prefix suffix (LPS) array and using this LPS array to perform the search.
function computeLPSArray(pattern) { const lps = new Array(pattern.length).fill(0); let length = 0; let i = 1; while (i < pattern.length) { if (pattern[i] === pattern[length]) { length++; lps[i] = length; i++; } else { if (length !== 0) { length = lps[length - 1]; } else { lps[i] = 0; i++; } } } return lps; } function KMPSearch(text, pattern) { const lps = computeLPSArray(pattern); let i = 0; // index for text let j = 0; // index for pattern while (i < text.length) { if (pattern[j] === text[i]) { i++; j++; } if (j === pattern.length) { console.log("Found pattern at index " + (i - j)); j = lps[j - 1]; } else if (i < text.length && pattern[j] !== text[i]) { if (j !== 0) { j = lps[j - 1]; } else { i++; } } } } // Example usage KMPSearch("abxabcabcaby", "abcaby");
Kadane’s Algorithm finds the maximum sum of a contiguous subarray within a numeric array. It iterates through the array, tracking the maximum sum encountered and the current subarray sum, ensuring a solution in O(n) time complexity.
Example:
function maxSubArraySum(arr) { let maxSoFar = arr[0]; let maxEndingHere = arr[0]; for (let i = 1; i < arr.length; i++) { maxEndingHere = Math.max(arr[i], maxEndingHere + arr[i]); maxSoFar = Math.max(maxSoFar, maxEndingHere); } return maxSoFar; } // Example usage: const array = [-2, 1, -3, 4, -1, 2, 1, -5, 4]; console.log(maxSubArraySum(array)); // Output: 6
To detect cycles in a directed graph using Depth-First Search (DFS), traverse the graph and track nodes in the recursion stack. If a node is already in the stack, a cycle is present.
Example:
function hasCycle(graph) { const visited = new Set(); const recStack = new Set(); function dfs(node) { if (recStack.has(node)) return true; if (visited.has(node)) return false; visited.add(node); recStack.add(node); for (let neighbor of graph[node]) { if (dfs(neighbor)) return true; } recStack.delete(node); return false; } for (let node in graph) { if (dfs(node)) return true; } return false; } // Example usage: const graph = { 0: [1], 1: [2], 2: [0], 3: [4], 4: [] }; console.log(hasCycle(graph)); // Output: true
Breadth-first search (BFS) traverses or searches tree or graph data structures, exploring neighbor nodes at the present depth before moving to the next depth level. BFS uses a queue to track nodes to be explored.
Example:
function bfs(graph, startNode) { let visited = new Set(); let queue = [startNode]; while (queue.length > 0) { let node = queue.shift(); if (!visited.has(node)) { console.log(node); visited.add(node); let neighbors = graph[node]; for (let neighbor of neighbors) { if (!visited.has(neighbor)) { queue.push(neighbor); } } } } } const graph = { 0: [1, 2], 1: [0, 3, 4], 2: [0, 5, 6], 3: [1], 4: [1], 5: [2], 6: [2] }; bfs(graph, 0);
Dynamic programming solves complex problems by breaking them into simpler subproblems. The coin change problem is a classic example where dynamic programming finds the minimum number of coins needed to make a given amount.
Example:
function coinChange(coins, amount) { const dp = Array(amount + 1).fill(Infinity); dp[0] = 0; for (let coin of coins) { for (let i = coin; i <= amount; i++) { dp[i] = Math.min(dp[i], dp[i - coin] + 1); } } return dp[amount] === Infinity ? -1 : dp[amount]; } console.log(coinChange([1, 2, 5], 11)); // Output: 3 console.log(coinChange([2], 3)); // Output: -1
To merge two sorted arrays, use a two-pointer technique. Iterate through both arrays, comparing elements, and append the smaller element to the result array to maintain sorted order.
function mergeSortedArrays(arr1, arr2) { let mergedArray = []; let i = 0, j = 0; while (i < arr1.length && j < arr2.length) { if (arr1[i] < arr2[j]) { mergedArray.push(arr1[i]); i++; } else { mergedArray.push(arr2[j]); j++; } } while (i < arr1.length) { mergedArray.push(arr1[i]); i++; } while (j < arr2.length) { mergedArray.push(arr2[j]); j++; } return mergedArray; } // Example usage: let array1 = [1, 3, 5]; let array2 = [2, 4, 6]; console.log(mergeSortedArrays(array1, array2)); // Output: [1, 2, 3, 4, 5, 6]