10 minute read

As I’m going through the process of completing my Bachelor’s Degree, I’ve been looking at some of the interview questions that have been asked of recent CS graduates. I’ve stumbled across a YouTuber named Coding Jesus who has a series of videos where people, generally CS graduates call in to essentially get a reality check on their skills while at the same time getting some advice on how to improve their skills.

The video I’m basing this post on is here.

Coding Interview Questions Based on the Video (JavaScript Focused)

General Questions

1. Basics of FizzBuzz Implementation:

  • Describe the FizzBuzz problem.

    Answer **FizzBuzz** is a classic programming problem often used in coding interviews to assess a candidate's basic programming skills and understanding of control flow. The problem is as follows: - Write a program that prints the numbers from 1 to 100. - For multiples of three, print "Fizz" instead of the number. - For multiples of five, print "Buzz" instead of the number. - For numbers which are multiples of both three and five, print "FizzBuzz". This problem tests the ability to use loops, conditionals, and modulus operations effectively. **Example Output:** ```plaintext 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz ... ```
  • How do you check if a number is divisible by another number in JavaScript?

    Answer In JavaScript, you can check if a number is divisible by another number using the **modulus operator (`%`)**. The modulus operator returns the remainder of the division of two numbers. If the remainder is `0`, it means the first number is divisible by the second. **Syntax:** ```javascript if (number % divisor === 0) { // number is divisible by divisor } ``` **Example:** ```javascript const number = 15; const divisor = 3; if (number % divisor === 0) { console.log(`${number} is divisible by ${divisor}`); } else { console.log(`${number} is not divisible by ${divisor}`); } // Output: 15 is divisible by 3 ```

2. Binary Search Tree:

  • Explain the structure of a Binary Search Tree.

    Answer A **Binary Search Tree (BST)** is a type of binary tree where each node has at most two children, commonly referred to as the left child and the right child. The structure of a BST adheres to the following properties: 1. **Left Subtree:** All nodes in the left subtree of a node contain values **less than** the node's value. 2. **Right Subtree:** All nodes in the right subtree of a node contain values **greater than** the node's value. 3. **No Duplicate Nodes:** Typically, BSTs do not allow duplicate values to ensure efficient search operations. **Visual Representation:** ```plaintext 8 / \ 3 10 / \ \ 1 6 14 / \ / 4 7 13 ``` In this example: - The root node is `8`. - All nodes in the left subtree of `8` (i.e., `3`, `1`, `6`, `4`, `7`) are less than `8`. - All nodes in the right subtree of `8` (i.e., `10`, `14`, `13`) are greater than `8`.
  • How would you search for a specific value in a BST using JavaScript?

    Answer To search for a specific value in a **Binary Search Tree (BST)** using JavaScript, you can implement either a **recursive** or an **iterative** approach. Below is an example of a recursive search function. **Recursive Search Function:** ```javascript class TreeNode { constructor(value) { this.value = value; this.left = null; this.right = null; } } function searchBST(root, target) { if (root === null) { return null; // Target not found } if (root.value === target) { return root; // Target found } else if (target < root.value) { return searchBST(root.left, target); // Search in left subtree } else { return searchBST(root.right, target); // Search in right subtree } } // Example Usage: const root = new TreeNode(8); root.left = new TreeNode(3); root.right = new TreeNode(10); root.left.left = new TreeNode(1); root.left.right = new TreeNode(6); root.left.right.left = new TreeNode(4); root.left.right.right = new TreeNode(7); root.right.right = new TreeNode(14); root.right.right.left = new TreeNode(13); const target = 6; const result = searchBST(root, target); if (result) { console.log(`Found node with value: ${result.value}`); } else { console.log('Target not found in the BST.'); } // Output: Found node with value: 6 ``` **Explanation:** - Start at the root node. - If the target value is equal to the current node's value, return the node. - If the target value is less than the current node's value, recursively search the left subtree. - If the target value is greater, recursively search the right subtree. - If the node is `null`, the target is not in the tree. **Time Complexity:** O(log n) on average for a balanced BST.
  • Would you use recursion or iteration to traverse the tree in JavaScript? Why?

    Answer Both **recursion** and **iteration** can be used to traverse a Binary Search Tree (BST) in JavaScript, and each has its own advantages: - **Recursion:** - **Pros:** - Simplifies the code, making it more readable and easier to implement, especially for **in-order**, **pre-order**, and **post-order** traversals. - Naturally aligns with the recursive structure of trees. - **Cons:** - Can lead to **stack overflow** issues if the tree is very deep due to excessive recursive calls. - Less control over the traversal process compared to iteration. - **Iteration:** - **Pros:** - More efficient in terms of memory usage as it avoids the overhead of recursive calls. - Prevents stack overflow issues, making it suitable for very deep trees. - Offers more control over the traversal process. - **Cons:** - Code can be more complex and harder to read. - Requires explicit use of data structures like stacks or queues to manage traversal state. **Recommendation:** - For **balanced and moderately sized trees**, **recursion** is often preferred due to its simplicity and readability. - For **very deep or unbalanced trees**, **iteration** is preferable to avoid potential stack overflow errors. **Example of Iterative In-Order Traversal:** ```javascript function inorderTraversal(root) { const stack = []; const result = []; let current = root; while (current !== null || stack.length > 0) { while (current !== null) { stack.push(current); current = current.left; } current = stack.pop(); result.push(current.value); current = current.right; } return result; } ``` **Time Complexity:** O(n) for both recursion and iteration.

3. Fundamental Data Structures:

  • Define an array in JavaScript and explain its characteristics.

    Answer In JavaScript, an **array** is a high-level, list-like object used to store multiple values in a single variable. Arrays can hold elements of different data types, including numbers, strings, objects, and even other arrays. **Characteristics of JavaScript Arrays:** 1. **Dynamic Sizing:** - Arrays can grow or shrink in size dynamically. You don't need to specify the size of an array upfront. 2. **Indexed Elements:** - Elements in an array are accessed using zero-based indices. For example, the first element is at index `0`. 3. **Heterogeneous Elements:** - Arrays can contain elements of different types. For example, `[1, "two", { three: 3 }]` is a valid array. 4. **Built-in Methods:** - JavaScript arrays come with a plethora of built-in methods for common operations, such as `push()`, `pop()`, `shift()`, `unshift()`, `map()`, `filter()`, `reduce()`, and more. 5. **Iterable:** - Arrays are iterable, meaning you can loop through their elements using loops like `for`, `for...of`, or array iteration methods. 6. **Sparse Arrays:** - JavaScript allows the creation of sparse arrays where certain indices may not have assigned values, resulting in `undefined` at those positions. **Example:** ```javascript const mixedArray = [42, "Hello", true, { name: "Alice" }, [1, 2, 3]]; console.log(mixedArray[0]); // Output: 42 console.log(mixedArray[3].name); // Output: Alice console.log(mixedArray[4][1]); // Output: 2 mixedArray.push("New Element"); console.log(mixedArray.length); // Output: 6 ``` **Use Cases:** - Storing lists of items, such as user data, product lists, or any collection of related elements. - Implementing other data structures like stacks, queues, or matrices.
  • What is the difference between linked lists and arrays in JavaScript?

    Answer **Arrays** and **Linked Lists** are both fundamental data structures used to store collections of elements, but they differ in their structure, performance characteristics, and use cases. | Feature | Arrays | Linked Lists | |-----------------------|-----------------------------------------------|-------------------------------------------------| | **Structure** | Contiguous memory blocks with indexed access.| Nodes containing data and pointers to next node.| | **Indexing** | Direct access via indices (e.g., `arr[0]`). | Sequential access; must traverse from head. | | **Dynamic Sizing** | Dynamically resizable but may require resizing.| Easily grow or shrink by adding/removing nodes. | | **Insertion/Deletion**| Inserting or deleting elements may require shifting elements, leading to O(n) time complexity.| Inserting or deleting nodes is O(1) if position is known. | | **Memory Overhead** | Less memory overhead per element. | Additional memory for pointers in each node. | | **Cache Performance** | Better cache locality due to contiguous storage.| Poor cache performance due to scattered nodes. | | **Use Cases** | Suitable for scenarios requiring frequent indexed access, like lookup tables.| Ideal for applications with frequent insertions/deletions, like implementing queues or stacks.| **Example Comparison:** - **Array:** ```javascript const array = [1, 2, 3, 4, 5]; // Access element at index 2 console.log(array[2]); // Output: 3 // Insert element at index 2 array.splice(2, 0, 'new'); console.log(array); // Output: [1, 2, "new", 3, 4, 5] ``` - **Linked List:** ```javascript class ListNode { constructor(value) { this.value = value; this.next = null; } } class LinkedList { constructor() { this.head = null; } append(value) { const newNode = new ListNode(value); if (!this.head) { this.head = newNode; return; } let current = this.head; while (current.next) { current = current.next; } current.next = newNode; } insertAfter(targetValue, newValue) { let current = this.head; while (current && current.value !== targetValue) { current = current.next; } if (current) { const newNode = new ListNode(newValue); newNode.next = current.next; current.next = newNode; } } // Additional methods like delete, find, etc., can be implemented similarly. } const list = new LinkedList(); list.append(1); list.append(2); list.append(3); list.insertAfter(2, 'new'); // Traversing the list let node = list.head; while (node) { console.log(node.value); node = node.next; } // Output: // 1 // 2 // "new" // 3 ``` **Conclusion:** - **Arrays** are preferable when you need fast access to elements via indices and the size doesn't change frequently. - **Linked Lists** are better suited for applications where frequent insertions and deletions occur, especially in the middle of the list.
  • Describe a use case for a linked list in a JavaScript application.

    Answer **Use Case: Implementing a Queue Data Structure** A **Queue** is a First-In-First-Out (FIFO) data structure commonly used in scenarios like task scheduling, handling asynchronous data streams, and managing requests. Implementing a queue using a **Linked List** in JavaScript provides efficient insertion and deletion operations. **Why Use a Linked List for a Queue:** - **Efficient Enqueue and Dequeue:** Both operations can be performed in O(1) time by maintaining pointers to both the head and tail of the list. - **Dynamic Size:** The queue can grow or shrink dynamically without the need for resizing, which is advantageous compared to using an array where resizing can be costly. - **Memory Utilization:** Linked lists allocate memory for each node individually, which can be more efficient in scenarios with frequent changes in the queue size. **Example Implementation:** ```javascript class ListNode { constructor(value) { this.value = value; this.next = null; } } class Queue { constructor() { this.head = null; // Points to the front of the queue this.tail = null; // Points to the end of the queue this.size = 0; } // Enqueue: Add an element to the end of the queue enqueue(value) { const newNode = new ListNode(value); if (!this.tail) { this.head = this.tail = newNode; } else { this.tail.next = newNode; this.tail = newNode; } this.size++; } // Dequeue: Remove an element from the front of the queue dequeue() { if (!this.head) { return null; // Queue is empty } const dequeuedValue = this.head.value; this.head = this.head.next; if (!this.head) { this.tail = null; } this.size--; return dequeuedValue; } // Peek: View the front element without removing it peek() { return this.head ? this.head.value : null; } // Check if the queue is empty isEmpty() { return this.size === 0; } // Get the size of the queue getSize() { return this.size; } } // Example Usage: const queue = new Queue(); queue.enqueue(10); queue.enqueue(20); queue.enqueue(30); console.log(queue.dequeue()); // Output: 10 console.log(queue.peek()); // Output: 20 console.log(queue.getSize()); // Output: 2 console.log(queue.isEmpty()); // Output: false ``` **Scenario:** - **Task Scheduling:** Managing tasks in the order they arrive, ensuring that the first task added is the first to be processed. - **Handling Asynchronous Requests:** Managing incoming requests to a server in the order they are received. **Benefits:** - **Scalability:** The queue can handle a large number of elements without performance degradation. - **Flexibility:** Easily accommodates dynamic workloads where tasks are frequently added and removed.