Welcome to AlgoCademy’s comprehensive guide to JavaScript coding challenges! Whether you’re a beginner looking to sharpen your skills or an experienced developer preparing for technical interviews at top tech companies, this article will provide you with a set of engaging challenges to test and improve your JavaScript prowess. As we dive into these challenges, remember that the key to mastering programming is not just about finding the right answer, but understanding the thought process and problem-solving techniques behind each solution.

Why JavaScript Coding Challenges Matter

Before we jump into the challenges, let’s briefly discuss why tackling coding problems is crucial for your development as a programmer:

Now, let’s dive into our 10 JavaScript coding challenges, ranging from beginner to advanced levels. For each challenge, we’ll provide a problem statement, followed by a solution and an explanation.

Challenge 1: Reverse a String

Difficulty: Beginner

Problem: Write a function that reverses a string. The input string is given as an array of characters.

Example:
Input: [“h”,”e”,”l”,”l”,”o”]
Output: [“o”,”l”,”l”,”e”,”h”]

Solution:

function reverseString(s) {
    return s.reverse();
}

// Test the function
console.log(reverseString(["h","e","l","l","o"]));

Explanation: This solution uses the built-in reverse() method in JavaScript, which reverses the elements of an array in place. While this is the simplest solution, it’s worth noting that in an interview setting, you might be asked to implement this without using built-in methods.

Challenge 2: Two Sum

Difficulty: Easy

Problem: Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.

Example:
Input: nums = [2,7,11,15], target = 9
Output: [0,1] (Because nums[0] + nums[1] == 9)

Solution:

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 [];
}

// Test the function
console.log(twoSum([2,7,11,15], 9));

Explanation: This solution uses a hash map to store the numbers we’ve seen so far and their indices. For each number, we calculate its complement (target – current number) and check if it exists in our map. If it does, we’ve found our pair and return their indices. If not, we add the current number and its index to the map and continue. This approach has a time complexity of O(n) and space complexity of O(n).

Challenge 3: Palindrome Number

Difficulty: Easy

Problem: Determine whether an integer is a palindrome. An integer is a palindrome when it reads the same backward as forward.

Example:
Input: 121
Output: true

Solution:

function isPalindrome(x) {
    if (x < 0) return false;
    return x === Number(x.toString().split('').reverse().join(''));
}

// Test the function
console.log(isPalindrome(121));
console.log(isPalindrome(-121));
console.log(isPalindrome(10));

Explanation: This solution first checks if the number is negative (which can’t be a palindrome due to the minus sign). Then, it converts the number to a string, splits it into an array of characters, reverses the array, joins it back into a string, and converts it back to a number. If this reversed number equals the original number, it’s a palindrome. While this solution is straightforward, it’s not the most efficient for very large numbers due to the string conversion.

Challenge 4: Valid Parentheses

Difficulty: Easy

Problem: Given a string s containing just the characters ‘(‘, ‘)’, ‘{‘, ‘}’, ‘[‘ and ‘]’, determine if the input string is valid. An input string is valid if:

  1. Open brackets must be closed by the same type of brackets.
  2. Open brackets must be closed in the correct order.

Example:
Input: s = “()[]{}”
Output: true

Solution:

function isValid(s) {
    const stack = [];
    const map = {
        '(': ')',
        '[': ']',
        '{': '}'
    };
    
    for (let i = 0; i < s.length; i++) {
        if (s[i] === '(' || s[i] === '[' || s[i] === '{') {
            stack.push(s[i]);
        } else {
            const last = stack.pop();
            if (s[i] !== map[last]) {
                return false;
            }
        }
    }
    
    return stack.length === 0;
}

// Test the function
console.log(isValid("()[]{}"));
console.log(isValid("([)]"));

Explanation: This solution uses a stack to keep track of opening brackets. When we encounter a closing bracket, we check if it matches the most recent opening bracket (which should be at the top of our stack). If it doesn’t match, or if we encounter a closing bracket when our stack is empty, the string is not valid. After processing all characters, we also need to ensure our stack is empty (all opening brackets were closed).

Challenge 5: Merge Two Sorted Lists

Difficulty: Easy

Problem: Merge two sorted linked lists and return it as a new sorted list. The new list should be made by splicing together the nodes of the first two lists.

Example:
Input: l1 = [1,2,4], l2 = [1,3,4]
Output: [1,1,2,3,4,4]

Solution:

function ListNode(val, next) {
    this.val = (val===undefined ? 0 : val)
    this.next = (next===undefined ? null : next)
}

function mergeTwoLists(l1, l2) {
    const dummy = new ListNode(0);
    let current = dummy;
    
    while (l1 !== null && l2 !== null) {
        if (l1.val < l2.val) {
            current.next = l1;
            l1 = l1.next;
        } else {
            current.next = l2;
            l2 = l2.next;
        }
        current = current.next;
    }
    
    if (l1 !== null) {
        current.next = l1;
    }
    if (l2 !== null) {
        current.next = l2;
    }
    
    return dummy.next;
}

// Test the function
const l1 = new ListNode(1, new ListNode(2, new ListNode(4)));
const l2 = new ListNode(1, new ListNode(3, new ListNode(4)));
console.log(mergeTwoLists(l1, l2));

Explanation: This solution uses a dummy node to simplify the merging process. We iterate through both lists simultaneously, always choosing the smaller value to add to our result list. Once we’ve exhausted one of the lists, we append any remaining nodes from the other list. This approach has a time complexity of O(n + m) where n and m are the lengths of the input lists.

Challenge 6: Maximum Subarray

Difficulty: Medium

Problem: Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:
Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
Output: 6 (Explanation: [4,-1,2,1] has the largest sum = 6.)

Solution:

function maxSubArray(nums) {
    let maxSum = nums[0];
    let currentSum = nums[0];
    
    for (let i = 1; i < nums.length; i++) {
        currentSum = Math.max(nums[i], currentSum + nums[i]);
        maxSum = Math.max(maxSum, currentSum);
    }
    
    return maxSum;
}

// Test the function
console.log(maxSubArray([-2,1,-3,4,-1,2,1,-5,4]));

Explanation: This solution uses Kadane’s algorithm, which is an efficient way to solve the maximum subarray problem. We iterate through the array once, keeping track of the maximum sum we’ve seen so far (maxSum) and the maximum sum ending at the current position (currentSum). At each step, we have two choices: start a new subarray at the current element, or extend the existing subarray. We choose whichever gives us a larger sum. This algorithm has a time complexity of O(n) and space complexity of O(1).

Challenge 7: Climbing Stairs

Difficulty: Easy

Problem: You are climbing a staircase. It takes n steps to reach the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Example:
Input: n = 3
Output: 3 (Explanation: There are three ways to climb to the top. 1. 1 step + 1 step + 1 step, 2. 1 step + 2 steps, 3. 2 steps + 1 step)

Solution:

function climbStairs(n) {
    if (n <= 2) return n;
    let first = 1;
    let second = 2;
    for (let i = 3; i <= n; i++) {
        let third = first + second;
        first = second;
        second = third;
    }
    return second;
}

// Test the function
console.log(climbStairs(3));
console.log(climbStairs(4));

Explanation: This problem is essentially asking for the nth Fibonacci number. We can solve it iteratively by keeping track of the two previous numbers in the sequence. At each step, we calculate the next number by adding the two previous ones. This solution has a time complexity of O(n) and space complexity of O(1).

Challenge 8: Best Time to Buy and Sell Stock

Difficulty: Easy

Problem: You are given an array prices where prices[i] is the price of a given stock on the ith day. You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock. Return the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return 0.

Example:
Input: prices = [7,1,5,3,6,4]
Output: 5 (Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.)

Solution:

function maxProfit(prices) {
    let minPrice = Infinity;
    let maxProfit = 0;
    
    for (let price of prices) {
        if (price < minPrice) {
            minPrice = price;
        } else if (price - minPrice > maxProfit) {
            maxProfit = price - minPrice;
        }
    }
    
    return maxProfit;
}

// Test the function
console.log(maxProfit([7,1,5,3,6,4]));
console.log(maxProfit([7,6,4,3,1]));

Explanation: This solution uses a single pass through the array. We keep track of the minimum price we’ve seen so far and the maximum profit we can achieve. For each price, we update the minimum price if necessary, and then check if selling at the current price would give us a better profit than what we’ve seen so far. This approach has a time complexity of O(n) and space complexity of O(1).

Challenge 9: Implement Queue using Stacks

Difficulty: Medium

Problem: Implement a first in first out (FIFO) queue using only two stacks. The implemented queue should support all the functions of a normal queue (push, pop, peek, empty).

Example:

MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek();  // returns 1
queue.pop();   // returns 1
queue.empty(); // returns false

Solution:

class MyQueue {
    constructor() {
        this.stack1 = [];
        this.stack2 = [];
    }

    push(x) {
        this.stack1.push(x);
    }

    pop() {
        this.peek();
        return this.stack2.pop();
    }

    peek() {
        if (this.stack2.length === 0) {
            while (this.stack1.length > 0) {
                this.stack2.push(this.stack1.pop());
            }
        }
        return this.stack2[this.stack2.length - 1];
    }

    empty() {
        return this.stack1.length === 0 && this.stack2.length === 0;
    }
}

// Test the queue
const queue = new MyQueue();
queue.push(1);
queue.push(2);
console.log(queue.peek());  // 1
console.log(queue.pop());   // 1
console.log(queue.empty()); // false

Explanation: This solution uses two stacks to implement a queue. The first stack is used for pushing elements, while the second stack is used for popping and peeking. When we need to pop or peek and the second stack is empty, we transfer all elements from the first stack to the second stack, effectively reversing their order. This ensures that the oldest element (which should be at the front of the queue) is now at the top of the second stack. The amortized time complexity for all operations is O(1).

Challenge 10: Longest Palindromic Substring

Difficulty: Medium

Problem: Given a string s, return the longest palindromic substring in s.

Example:
Input: s = “babad”
Output: “bab” (Note: “aba” is also a valid answer.)

Solution:

function longestPalindrome(s) {
    if (s.length < 2) return s;
    let start = 0, maxLength = 1;
    
    function expandAroundCenter(left, right) {
        while (left >= 0 && right < s.length && s[left] === s[right]) {
            const currentLength = right - left + 1;
            if (currentLength > maxLength) {
                start = left;
                maxLength = currentLength;
            }
            left--;
            right++;
        }
    }
    
    for (let i = 0; i < s.length; i++) {
        expandAroundCenter(i - 1, i + 1);  // Odd length palindromes
        expandAroundCenter(i, i + 1);      // Even length palindromes
    }
    
    return s.substring(start, start + maxLength);
}

// Test the function
console.log(longestPalindrome("babad"));
console.log(longestPalindrome("cbbd"));

Explanation: This solution uses the expand around center approach. For each character in the string, we consider it as a potential center of a palindrome and expand outwards, checking if the characters on both sides match. We do this for both odd-length palindromes (where the center is a single character) and even-length palindromes (where the center is between two characters). We keep track of the start index and length of the longest palindrome we’ve found. This approach has a time complexity of O(n^2) and space complexity of O(1).

Conclusion

Congratulations on making it through these 10 JavaScript coding challenges! By working through these problems, you’ve exercised your problem-solving skills, deepened your understanding of JavaScript, and prepared yourself for the types of questions you might encounter in technical interviews at top tech companies.

Remember, the key to mastering these challenges is not just in finding the correct solution, but in understanding the reasoning behind each approach. As you continue your coding journey, try to:

Keep practicing, stay curious, and don’t be afraid to tackle more complex challenges as you grow. With consistent effort and a strategic approach to learning, you’ll be well on your way to acing those technical interviews and becoming a skilled JavaScript developer.

Happy coding, and remember that AlgoCademy is here to support you every step of the way in your programming journey!