Coding interview questions & answers

20 coding interview questions with complete model answers. The bank holds 1820 coding questions across every role and company we cover.

CodingEasyData ScientistCoding screen

1. Given a pandas DataFrame df with columns ‘Date’, ‘Sales’, and ‘Customer_Rating’, write a Python code snippet to clean this DataFrame.

The full question

Given a pandas DataFrame df with columns ‘Date’, ‘Sales’, and ‘Customer_Rating’, write a Python code snippet to clean this DataFrame. Assume there are missing values in ‘Customer_Rating’ and duplicate rows across all columns. Remove duplicates and replace missing values in ‘Customer_Rating’ with the average rating.

Model answer

The flow

  1. Clarify inputs & output shape: Understand the DataFrame structure and the requirements for cleaning.
  2. Brute force first: Implement straightforward solutions for removing duplicates and handling missing values.
  3. Optimize: Utilize pandas built-in functions to efficiently perform the operations.
  4. State complexity: Consider the time complexity of operations, especially with large datasets.
  5. Test the edges: Ensure the solution handles edge cases like all values missing or no duplicates.

The answer

1. Clarify inputs & output shape

  • We have a DataFrame df with columns Date, Sales, and Customer_Rating.
  • The task is to remove duplicate rows and fill missing values in Customer_Rating with the average rating.

2. Brute force first

  • Start by identifying duplicate rows and removing them.
  • Calculate the average of Customer_Rating and use it to fill missing values.

3. Optimize

  • Use pandas functions like drop_duplicates() and fillna() to efficiently clean the DataFrame.
import pandas as pd

# Sample DataFrame
# df = pd.DataFrame({
#     'Date': [...],
#     'Sales': [...],
#     'Customer_Rating': [...]
# })

# Remove duplicate rows
cleaned_df = df.drop_duplicates()

# Calculate the mean of Customer_Rating, ignoring NaN values
average_rating = cleaned_df['Customer_Rating'].mean()

# Fill missing values in Customer_Rating with the average rating
cleaned_df['Customer_Rating'].fillna(average_rating, inplace=True)
  • Approach:
  • drop_duplicates() removes all duplicate rows based on all columns.
  • mean() calculates the average of Customer_Rating, ignoring NaN values.
  • fillna() replaces NaN values with the calculated average.
  • Complexity: The time complexity is approximately $O(n)$ for both removing duplicates and filling NaN values, where $n$ is the number of rows in the DataFrame.

4. Test the edges

  • Ensure the solution works when all Customer_Rating values are missing, or when there are no duplicates.

Why this works

  • Testing understanding: The interviewer is assessing your ability to use pandas for data cleaning tasks.
  • Efficiency: Using pandas built-in functions ensures operations are performed efficiently on potentially large datasets.
  • Edge cases: A strong answer considers edge cases, such as all values missing or no duplicates, ensuring robustness.
  • Weak answers: Failing to handle missing values correctly or not removing duplicates would indicate a lack of attention to detail or understanding of pandas capabilities.
CodingEasySoftware EngineerTechnical Screen

2. You are asked to solve two separate coding questions.

The full question

You are asked to solve two separate coding questions. You do not need to run code; be prepared to explain your approach and walk through examples.

Question 1: Near-palindrome with one deletion

Given a string s, determine whether it can become a palindrome after deleting at most one character.

  • Input: a string s (consisting of lowercase English letters)
  • Output: true if s can be made a palindrome by removing 0 or 1 character; otherwise false
  • Constraints (typical): 1 <= len(s) <= 1e5

Example:

  • s = "abca"true (delete 'b' or 'c')
  • s = "abc"false

Question 2: Binary tree vertical order traversal

Given the root of a binary tree, return its vertical order traversal.

Define a node’s column as follows:

  • Root is at column 0
  • Left child is column col - 1
  • Right child is column col + 1

Return a list of columns from leftmost to rightmost. Within each column, list nodes in top-to-bottom order. If multiple nodes share the same row and column, order them in the same order they would appear in a level-order (BFS) traversal from left to right.

  • Input: root of a binary tree
  • Output: List[List[int]] (values grouped by column)
  • Constraints (typical): up to 1e41e5 nodes

Example: For the tree:

  • 3 as root
  • left child 9, right child 8
  • 9 has children 4 and 0
  • 8 has children 1 and 7

Vertical order output:

  • [[4], [9], [3, 0, 1], [8], [7]]

Explain your algorithm and its time/space complexity.

Model answer

// Question 1: Near-palindrome with one deletion
function validPalindrome(s) {
    function isPalindrome(l, r) {
        while (l < r) {
            if (s[l] !== s[r]) return false;
            l++;
            r--;
        }
        return true;
    }

    let left = 0;
    let right = s.length - 1;

    while (left < right) {
        if (s[left] !== s[right]) {
            // Try removing one character from either end
            return isPalindrome(left + 1, right) || isPalindrome(left, right - 1);
        }
        left++;
        right--;
    }
    return true;
}

// Approach for Question 1:
// - Use two pointers to check if the string is a palindrome.
// - If a mismatch is found, check if removing one of the mismatched characters results in a palindrome.
// - This is done by checking two substrings: one excluding the left character and one excluding the right character.

// Complexity for Question 1:
// Time: O(n), where n is the length of the string, as we may need to check the entire string.
// Space: O(1), as we use a constant amount of extra space.


// Question 2: Binary tree vertical order traversal
function verticalOrder(root) {
    if (!root) return [];

    const columnTable = new Map();
    const queue = [{ node: root, col: 0 }];
    let minCol = 0, maxCol = 0;

    while (queue.length > 0) {
        const { node, col } = queue.shift();

        if (!columnTable.has(col)) {
            columnTable.set(col, []);
        }
        columnTable.get(col).push(node.val);

        if (node.left) {
            queue.push({ node: node.left, col: col - 1 });
            minCol = Math.min(minCol, col - 1);
        }
        if (node.right) {
            queue.push({ node: node.right, col: col + 1 });
            maxCol = Math.max(maxCol, col + 1);
        }
    }

    const result = [];
    for (let i = minCol; i <= maxCol; i++) {
        result.push(columnTable.get(i));
    }
    return result;
}

// Approach for Question 2:
// - Use a BFS approach to traverse the tree while keeping track of the column index for each node.
// - Store nodes in a map where keys are column indices and values are lists of node values.
// - Track the minimum and maximum column indices to determine the range of columns to output.
// - Collect results from the map in order from the smallest to the largest column index.

// Complexity for Question 2:
// Time: O(n), where n is the number of nodes in the tree, as each node is processed once.
// Space: O(n), for the map and queue used to store nodes and their column indices.
CodingEasySoftware EngineerTechnical Screen

3. You are given two separate coding tasks.

The full question

You are given two separate coding tasks.

---

Problem 1: Deep copy a linked list with extra pointers

You are given the head of a singly linked list. Each node has three fields:

  • val: an integer value
  • next: a pointer (or reference) to the next node in the list, or null if it is the last node
  • random: a pointer (or reference) to any node in the list (including possibly itself) or null

The list may contain zero or more nodes.

Task: Implement a function that creates a deep copy of this list. The new list must:

  • Contain the same number of nodes as the original.
  • Preserve the val values.
  • Preserve the structure of both the next and random pointers: for every original node, its copy's next and random should point to the copies of the corresponding original targets.
  • Share no nodes with the original list (i.e., all nodes in the copied list must be newly allocated).

Return the head of the copied list.

You may assume:

  • Number of nodes (n) satisfies (0 \leq n \leq 10^5).
  • The input list may contain arbitrary random pointer configurations, including cycles formed via random pointers.

You should aim for (O(n)) time complexity and (O(n)) additional space.

---

Problem 2: Find the k most frequent integers in an array

You are given an integer array nums and an integer k where (1 \leq k \leq \text{number of distinct elements in } nums).

Task: Return any order of the k distinct integers that appear most frequently in nums.

  • If multiple numbers have the same frequency and they are in the top k by frequency, any order among them is acceptable.
  • The output should contain exactly k distinct intege

Model answer

// Problem 1: Deep copy a linked list with extra pointers

function copyRandomList(head) {
    if (!head) return null;

    // Step 1: Create new nodes and interleave them with the original nodes
    let current = head;
    while (current) {
        const newNode = new Node(current.val);
        newNode.next = current.next;
        current.next = newNode;
        current = newNode.next;
    }

    // Step 2: Assign random pointers for the new nodes
    current = head;
    while (current) {
        if (current.random) {
            current.next.random = current.random.next;
        }
        current = current.next.next;
    }

    // Step 3: Separate the new list from the original list
    current = head;
    const newHead = head.next;
    while (current) {
        const newNode = current.next;
        current.next = newNode.next;
        if (newNode.next) {
            newNode.next = newNode.next.next;
        }
        current = current.next;
    }

    return newHead;
}

// Problem 2: Find the k most frequent integers in an array

function topKFrequent(nums, k) {
    const frequencyMap = new Map();

    // Count the frequency of each number
    for (const num of nums) {
        frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1);
    }

    // Create an array of buckets to store numbers by frequency
    const buckets = Array(nums.length + 1).fill().map(() => []);
    for (const [num, freq] of frequencyMap.entries()) {
        buckets[freq].push(num);
    }

    // Gather the top k frequent elements
    const result = [];
    for (let i = buckets.length - 1; i >= 0 && result.length < k; i--) {
        if (buckets[i].length > 0) {
            result.push(...buckets[i]);
        }
    }

    return result.slice(0, k);
}
  • Approach for Problem 1:
  • Interleave Nodes: Create new nodes and interleave them with the original nodes.
  • Assign Random Pointers: Set the random pointers for the new nodes using the interleaved structure.
  • Separate Lists: Detach the new list from the original list to form the deep copy.
  • Approach for Problem 2:
  • Frequency Map: Use a hash map to count the frequency of each element.
  • Bucket Sort: Use an array of buckets where the index represents frequency.
  • Collect Top k: Collect elements from the highest frequency bucket downwards until k elements are gathered.

Complexity:

  • Time Complexity: Both solutions run in O(n) time, where n is the number of nodes or elements.
  • Space Complexity: O(n) additional space is used for both solutions, primarily for the new nodes and frequency map.
CodingEasyData ScientistTechnical Screen

4. You are given a binary classifier’s outputs on a dataset: y_true: array of true labels in ({0,1}) y_score: array of predicted scores/probabilities…

The full question

You are given a binary classifier’s outputs on a dataset:

  • y_true: array of true labels in ({0,1})
  • y_score: array of predicted scores/probabilities (higher means more likely positive)

Tasks

  1. Define precision and recall.
  2. Describe how to compute the precision–recall curve by sweeping a decision threshold over y_score.
  3. Implement (in pseudocode or Python) a function that returns PR curve points:
  • Output arrays: thresholds, precision, recall
  1. Mention at least two edge cases/pitfalls (e.g., ties in scores, no predicted positives at a threshold, extreme class imbalance).

Optional: Explain how to compute Average Precision / AUPRC and what the baseline means.

Model answer

import numpy as np

def precision_recall_curve(y_true, y_score):
    # Sort scores and corresponding true labels in descending order
    desc_score_indices = np.argsort(y_score)[::-1]
    y_true = np.array(y_true)[desc_score_indices]
    y_score = np.array(y_score)[desc_score_indices]

    # Initialize variables
    thresholds = []
    precision = []
    recall = []
    tp = 0  # True positives
    fp = 0  # False positives
    fn = np.sum(y_true)  # False negatives initially all positives

    # Iterate through scores to calculate precision and recall
    for i in range(len(y_score)):
        if i == 0 or y_score[i] != y_score[i - 1]:
            thresholds.append(y_score[i])
            precision.append(tp / (tp + fp) if (tp + fp) > 0 else 1.0)
            recall.append(tp / (tp + fn) if (tp + fn) > 0 else 0.0)

        if y_true[i] == 1:
            tp += 1
            fn -= 1
        else:
            fp += 1

    # Add the last point at threshold 0
    thresholds.append(0)
    precision.append(tp / (tp + fp) if (tp + fp) > 0 else 1.0)
    recall.append(tp / (tp + fn) if (tp + fn) > 0 else 0.0)

    return thresholds, precision, recall

# Example usage
y_true = [0, 1, 1, 0, 1]
y_score = [0.1, 0.4, 0.35, 0.8, 0.7]
thresholds, precision, recall = precision_recall_curve(y_true, y_score)
print("Thresholds:", thresholds)
print("Precision:", precision)
print("Recall:", recall)
  • Precision is the ratio of true positives to the sum of true positives and false positives. It measures the accuracy of positive predictions.
  • Recall is the ratio of true positives to the sum of true positives and false negatives. It measures the ability to find all positive samples.
  • To compute the precision-recall curve, sort y_score in descending order and iterate over it. For each unique score, calculate precision and recall, updating true positives, false positives, and false negatives accordingly.

Complexity:

  • Time: O(n log n) due to sorting, where n is the number of samples.
  • Space: O(n) for storing sorted indices and result arrays.

Edge Cases/Pitfalls:

  1. Ties in scores: Ensure that precision and recall are calculated correctly when multiple instances have the same score.
  2. No predicted positives: Handle cases where no samples are predicted as positive at a certain threshold, which can lead to division by zero in precision calculation.

Average Precision / AUPRC:

  • Average Precision (AP) is the area under the precision-recall curve. It summarizes the precision-recall trade-off across thresholds. The baseline for AP is the proportion of positive samples in the dataset.
CodingEasyData ScientistTechnical Screen

5. Two players play independent games of table tennis.

The full question

Two players play independent games of table tennis.

  • Player A wins each game with probability (p) (constant across games).
  • They play a best-of-5 match (first to 3 wins).
  • Let (X) be the number of games played until the match ends (so (X\in{3,4,5})).

Tasks

1) Compute (\theta = \Pr(X=5)). 2) Write a simulation (Monte Carlo) approach to estimate (\theta). 3) Follow-up: given (M) simulated matches, how would you build a confidence interval for (\theta)?

Model answer

// Function to compute the probability that a match goes to 5 games
function probabilityOfFiveGames(p) {
  // Probability that the match goes to 5 games
  // This happens if both players win 2 games each in the first 4 games
  const prob = 6 * Math.pow(p, 2) * Math.pow(1 - p, 2);
  return prob;
}

// Monte Carlo simulation to estimate the probability of a match going to 5 games
function simulateMatches(numMatches, p) {
  let fiveGameMatches = 0;

  for (let i = 0; i < numMatches; i++) {
    let winsA = 0;
    let winsB = 0;
    let gamesPlayed = 0;

    while (winsA < 3 && winsB < 3) {
      gamesPlayed++;
      if (Math.random() < p) {
        winsA++;
      } else {
        winsB++;
      }
    }

    if (gamesPlayed === 5) {
      fiveGameMatches++;
    }
  }

  return fiveGameMatches / numMatches;
}

// Function to build a confidence interval for the estimated probability
function confidenceInterval(numMatches, p, confidenceLevel = 0.95) {
  const thetaEstimate = simulateMatches(numMatches, p);
  const z = 1.96; // z-score for 95% confidence
  const marginOfError = z * Math.sqrt((thetaEstimate * (1 - thetaEstimate)) / numMatches);

  return [thetaEstimate - marginOfError, thetaEstimate + marginOfError];
}

// Example usage
const p = 0.5; // Probability that player A wins a game
const numMatches = 10000; // Number of simulated matches
console.log("Probability of 5 games (analytical):", probabilityOfFiveGames(p));
console.log("Probability of 5 games (simulation):", simulateMatches(numMatches, p));
console.log("95% Confidence Interval:", confidenceInterval(numMatches, p));
  • Analytical Calculation: The probability that a match goes to 5 games is calculated by considering the scenarios where both players win exactly 2 games each in the first 4 games. This is given by \(6 \times p^2 \times (1-p)^2\), where 6 is the number of ways to arrange 2 wins for each player in 4 games.
  • Monte Carlo Simulation: Simulate a large number of matches, counting how many go to 5 games. For each match, simulate games until one player wins 3 games. Count matches where exactly 5 games are played.
  • Confidence Interval: Use the normal approximation for the binomial distribution to calculate the confidence interval for the estimated probability. The margin of error is calculated using the standard error and the z-score for the desired confidence level.

Complexity:

  • Time: \(O(M)\) for the simulation, where \(M\) is the number of matches.
  • Space: \(O(1)\), constant space usage.
CodingEasyData ScientistCoding screen

6. Describe a scenario where you would write a Python script to process and analyze raw text data.

The full question

Describe a scenario where you would write a Python script to process and analyze raw text data. What steps would you take in your script?

Model answer

The flow

  1. Clarify inputs & output shape: Define the format of the raw text data and the expected output.
  2. Brute force first: Write a simple script to read and process the text data.
  3. Optimize: Improve the script for efficiency and scalability.
  4. State complexity: Analyze the time and space complexity of the script.
  5. Test the edges: Ensure the script handles edge cases and unexpected inputs.

The answer

Clarify inputs & output shape

  • The raw text data is a collection of text files, each containing multiple lines of text.
  • The goal is to analyze the frequency of words and output a summary report in CSV format.

Brute force first

  • Start by writing a Python script that opens each text file and reads its contents.
  • Use a dictionary to count the occurrences of each word across all files.
import os
import csv
from collections import defaultdict

# Directory containing text files
directory = 'text_data/'

# Dictionary to store word frequencies
word_count = defaultdict(int)

# Read and process each file
for filename in os.listdir(directory):
    if filename.endswith('.txt'):
        with open(os.path.join(directory, filename), 'r') as file:
            for line in file:
                # Tokenize the line into words
                words = line.strip().split()
                for word in words:
                    # Convert to lowercase and count
                    word_count[word.lower()] += 1

# Write the word frequencies to a CSV file
with open('word_frequencies.csv', 'w', newline='') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerow(['Word', 'Frequency'])
    for word, count in word_count.items():
        writer.writerow([word, count])

Optimize

  • Use more efficient data structures or libraries such as collections.Counter for counting.
  • Implement parallel processing if dealing with a large number of files.

State complexity

  • Time complexity: $O(n)$, where $n$ is the total number of words across all files.
  • Space complexity: $O(m)$, where $m$ is the number of unique words.

Test the edges

  • Test with files containing special characters, numbers, or empty lines.
  • Ensure the script handles cases where the directory is empty or files are missing.

Why this works

  • Interviewer is testing: Ability to process text data, write efficient code, and handle edge cases.
  • Sanity check: Ensures that the candidate can handle common issues in text processing, such as case sensitivity and special characters.
  • Weak answers fail: If a candidate doesn't optimize for large datasets or fails to handle edge cases, the solution won't scale or be robust.
CodingEasySoftware EngineerTake-home Project

7. You are asked to solve the following four independent coding problems.

The full question

You are asked to solve the following four independent coding problems.

---

1) Block Placement Simulator (Tetris-like)

You have an empty n x m grid (rows indexed top-to-bottom, columns left-to-right). You will place a sequence of pieces, each piece being one of five types A/B/C/D/E.

Each piece type is defined by a fixed set of occupied cells in its own local coordinate system (e.g., a small boolean matrix or a list of (dr, dc) offsets). Pieces:

  • cannot be rotated or flipped,
  • must be placed fully within the grid,
  • cannot overlap already-occupied cells.

Placement rule (scan order): For each incoming piece, find the first valid placement position by scanning candidate top-left anchors in priority order: 1) smallest row index (top to bottom), 2) within the same row, smallest column index (left to right).

Place the piece at that first valid position. If no valid position exists, stop and return the grid/state according to the output requirement.

Task: Implement the simulator that processes the piece sequence and produces the final grid (or alternatively the number of pieces placed), following the scan-order placement rule.

---

2) Longest Continuous Houses After Each Build

On an integer number line, you build houses one by one at positions given by an array queries, in order. After each build, you must output the current length of the longest contiguous segment of built houses, where “contiguous” means consecutive integer positions.

Example: if houses exist at {2,3,4,10}, the longest contiguous segment length is 3 (for 2-4).

Notes:

  • Coordinates can be very large (e.g., up to billions),
  • The number of builds is manageable,
  • Duplicate builds (building again at an already-built coordinate) should be

Model answer

function blockPlacementSimulator(n, m, pieces, pieceShapes) {
    // Initialize an empty grid
    const grid = Array.from({ length: n }, () => Array(m).fill(false));

    // Helper function to check if a piece can be placed at a given position
    function canPlacePiece(pieceShape, startRow, startCol) {
        for (const [dr, dc] of pieceShape) {
            const newRow = startRow + dr;
            const newCol = startCol + dc;
            if (
                newRow < 0 || newRow >= n || 
                newCol < 0 || newCol >= m || 
                grid[newRow][newCol]
            ) {
                return false;
            }
        }
        return true;
    }

    // Helper function to place a piece on the grid
    function placePiece(pieceShape, startRow, startCol) {
        for (const [dr, dc] of pieceShape) {
            grid[startRow + dr][startCol + dc] = true;
        }
    }

    // Process each piece
    for (const pieceType of pieces) {
        const pieceShape = pieceShapes[pieceType];
        let placed = false;

        // Scan the grid to find the first valid position
        for (let row = 0; row < n && !placed; row++) {
            for (let col = 0; col < m && !placed; col++) {
                if (canPlacePiece(pieceShape, row, col)) {
                    placePiece(pieceShape, row, col);
                    placed = true;
                }
            }
        }

        // If a piece cannot be placed, stop processing
        if (!placed) {
            break;
        }
    }

    return grid;
}

// Example usage
const n = 5, m = 5;
const pieces = ['A', 'B', 'C'];
const pieceShapes = {
    'A': [[0, 0], [0, 1]], // Example shape for piece A
    'B': [[0, 0], [1, 0]], // Example shape for piece B
    'C': [[0, 0], [0, 1], [1, 0]], // Example shape for piece C
    // Define other shapes as needed
};

console.log(blockPlacementSimulator(n, m, pieces, pieceShapes));
  • Approach:
  • Initialize an n x m grid with all cells set to false (unoccupied).
  • For each piece in the sequence, attempt to place it by scanning the grid from top-left to bottom-right.
  • Use helper functions to check if a piece can be placed and to place the piece if possible.
  • Stop processing if a piece cannot be placed due to lack of space.
  • Complexity:
  • Time: O(p n m * s), where p is the number of pieces, n and m are grid dimensions, and s is the maximum size of a piece.
  • Space: O(n * m) for the grid.
CodingEasySoftware EngineerOnsite

8. You inherit a Python thread-pool implementation that accepts callables and runs them on a fixed number of worker threads.

The full question

You inherit a Python thread-pool implementation that accepts callables and runs them on a fixed number of worker threads. Extend it so submit(fn, priority) schedules lower numeric priorities before higher numeric priorities, while tasks with the same priority run in submission order. Explain how you would debug the existing implementation and make shutdown, exceptions, and concurrent submissions safe.

Model answer

import threading
import queue
import time

class PriorityThreadPoolExecutor:
    def __init__(self, max_workers):
        self.task_queue = queue.PriorityQueue()
        self.threads = []
        self.shutdown_flag = threading.Event()
        
        for _ in range(max_workers):
            thread = threading.Thread(target=self._worker)
            thread.start()
            self.threads.append(thread)

    def submit(self, fn, priority=0):
        # Use a tuple (priority, count, fn) to ensure tasks with the same priority maintain order
        count = time.time()  # Use timestamp to maintain order for tasks with the same priority
        self.task_queue.put((priority, count, fn))

    def _worker(self):
        while not self.shutdown_flag.is_set():
            try:
                priority, count, fn = self.task_queue.get(timeout=1)
                try:
                    fn()
                except Exception as e:
                    print(f"Exception in task: {e}")
                finally:
                    self.task_queue.task_done()
            except queue.Empty:
                continue

    def shutdown(self, wait=True):
        self.shutdown_flag.set()
        if wait:
            for thread in self.threads:
                thread.join()

# Example usage
def example_task():
    print("Task executed")

executor = PriorityThreadPoolExecutor(max_workers=3)
executor.submit(example_task, priority=1)
executor.submit(example_task, priority=0)
executor.shutdown()
  • Approach:
  • Use a PriorityQueue to manage tasks, ensuring lower numeric priorities are processed first.
  • Each task is stored as a tuple (priority, count, fn) where count is a timestamp to maintain order for tasks with the same priority.
  • Worker threads continuously fetch tasks from the queue and execute them.
  • Implement a shutdown mechanism using a threading event to safely stop worker threads.
  • Debugging and Safety:
  • Shutdown: Use a threading event to signal shutdown, ensuring threads can exit gracefully.
  • Exceptions: Wrap task execution in a try-except block to handle exceptions without crashing the worker.
  • Concurrent Submissions: The PriorityQueue is thread-safe, handling concurrent submissions without additional locking.

Complexity:

  • Time: O(1) for submitting tasks; O(log n) for retrieving tasks from the queue.
  • Space: O(n) for storing tasks in the queue, where n is the number of tasks.
CodingEasySoftware EngineerTechnical Screen

9. You are given two independent programming problems.

The full question

You are given two independent programming problems.

---

Problem 1: Implement a bounded key–value cache

Design a data structure that stores key–value pairs with the following behavior:

  • The cache is initialized with a positive integer capacity.
  • It supports two operations:
  • get(key): return the value associated with key if it exists; otherwise return -1.
  • put(key, value): insert or update the key–value pair.
  • When inserting a new key and the cache is at full capacity, it must evict one existing entry.
  • The entry to evict must be the least recently used (LRU) key. "Use" means any successful get or put on that key.
  • After a get(key) or put(key, value), that key becomes the most recently used.
  • All operations (get and put) should run in amortized O(1) time.

Assume:

  • Keys and values are integers.
  • There can be up to 10^5 operations.

Define the class and methods in a language of your choice, for example:

def __init__(self, capacity: int):
pass

def get(self, key: int) -> int:
pass

def put(self, key: int, value: int) -> None:
pass

Describe the data structures you use and implement the methods.

---

Problem 2: Assign secret gift givers (Secret Santa)

You are given a CSV-formatted list of people and their email addresses. Each row has the fields:

  • name
  • email

Example input (including header):

name,email
Alice,alice@example.com
Bob,bob@example.com
Charlie,charlie@example.com

You need to write a program that assigns each person a recipient to whom they will give a gift, subject to these rules:

  1. No one can be assigned to give a gift to themselves.
  2. Each person must give a gift t

Model answer

class LRUCache {
  constructor(capacity) {
    this.capacity = capacity; // Maximum capacity of the cache
    this.cache = new Map(); // Map to store key-value pairs
  }

  get(key) {
    if (!this.cache.has(key)) {
      return -1; // Return -1 if the key is not found
    }
    const value = this.cache.get(key);
    // Move the accessed key to the end to mark it as recently used
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }

  put(key, value) {
    if (this.cache.has(key)) {
      // If key exists, delete it to update its position
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
      // If cache is full, remove the least recently used (first) key
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    // Insert the key-value pair as the most recently used
    this.cache.set(key, value);
  }
}

// Approach:
// - Use a Map to store key-value pairs, as it maintains insertion order.
// - On `get`, check if the key exists. If yes, move it to the end of the Map.
// - On `put`, if the key exists, update its position. If the cache is full, remove the least recently used key.
// - This ensures both operations run in O(1) amortized time due to the properties of Map.

// Complexity:
// - Time: O(1) for both `get` and `put` operations.
// - Space: O(capacity) for storing the key-value pairs.

Problem 2: Assign Secret Gift Givers (Secret Santa)

function assignSecretSanta(participants) {
  const n = participants.length;
  const shuffled = [...participants];

  // Shuffle the array to randomize assignments
  for (let i = n - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }

  // Ensure no one is assigned to themselves
  for (let i = 0; i < n; i++) {
    if (shuffled[i] === participants[i]) {
      // Swap with the next person, or the first if it's the last person
      const swapIndex = i === n - 1 ? 0 : i + 1;
      [shuffled[i], shuffled[swapIndex]] = [shuffled[swapIndex], shuffled[i]];
    }
  }

  // Create the assignments
  const assignments = participants.map((person, index) => ({
    giver: person,
    receiver: shuffled[index],
  }));

  return assignments;
}

// Approach:
// - Shuffle the list of participants to randomize the order.
// - Ensure no participant is assigned to themselves by checking and swapping if necessary.
// - Return a list of assignments where each participant is paired with a different one.

// Complexity:
// - Time: O(n), where n is the number of participants, due to the shuffle and swap operations.
// - Space: O(n) for storing the shuffled list and assignments.
CodingEasySoftware EngineerTechnical Screen

10. You are given three independent algorithmic tasks.

The full question

You are given three independent algorithmic tasks. For each one, explain your approach (no need to run code).

1) Merge two sorted lists (integers instead of nodes)

Given two sorted integer arrays A and B (non-decreasing order), merge them into a single sorted array containing all elements from both inputs.

  • Input: two arrays A, B
  • Output: merged sorted array
  • Constraints (typical): 0 <= len(A), len(B) <= 1e5

2) Max profit from one stock transaction

Given an array prices where prices[i] is the stock price on day i, compute the maximum profit you can achieve by choosing at most one day to buy and a later day to sell. If no profit is possible, return 0.

  • Input: array prices
  • Output: integer max profit
  • Constraints (typical): 1 <= n <= 1e5, 0 <= prices[i] <= 1e9

3) Validate parentheses pairing

Given a string s consisting only of the characters '(', ')', '[', ']', '{', '}', determine whether the parentheses/brackets are valid.

A string is valid if:

  • Every opening bracket has a corresponding closing bracket of the same type.
  • Brackets are closed in the correct order.
  • Input: string s
  • Output: boolean
  • Constraints (typical): 0 <= |s| <= 1e5

Model answer

// 1) Merge two sorted lists
function mergeSortedArrays(A, B) {
    let i = 0, j = 0;
    const merged = [];

    // Compare elements from both arrays and add the smaller one to the merged array
    while (i < A.length && j < B.length) {
        if (A[i] < B[j]) {
            merged.push(A[i]);
            i++;
        } else {
            merged.push(B[j]);
            j++;
        }
    }

    // Add remaining elements from A, if any
    while (i < A.length) {
        merged.push(A[i]);
        i++;
    }

    // Add remaining elements from B, if any
    while (j < B.length) {
        merged.push(B[j]);
        j++;
    }

    return merged;
}

// 2) Max profit from one stock transaction
function maxProfit(prices) {
    let minPrice = Infinity;
    let maxProfit = 0;

    for (let price of prices) {
        // Update minPrice to the lowest price seen so far
        if (price < minPrice) {
            minPrice = price;
        }
        // Calculate profit if selling at the current price
        const profit = price - minPrice;
        // Update maxProfit if the current profit is greater
        if (profit > maxProfit) {
            maxProfit = profit;
        }
    }

    return maxProfit;
}

// 3) Validate parentheses pairing
function isValidParentheses(s) {
    const stack = [];
    const map = {
        ')': '(',
        ']': '[',
        '}': '{'
    };

    for (let char of s) {
        if (char in map) {
            // Check if the stack's top matches the current closing bracket
            if (stack.length > 0 && stack[stack.length - 1] === map[char]) {
                stack.pop();
            } else {
                return false;
            }
        } else {
            // Push opening brackets onto the stack
            stack.push(char);
        }
    }

    // If stack is empty, all brackets were matched
    return stack.length === 0;
}
  • Merge two sorted lists: Use two pointers to traverse both arrays, adding the smaller element to the result array. Continue until all elements from both arrays are added.
  • Max profit from one stock transaction: Track the minimum price seen so far and calculate potential profit at each price. Update the maximum profit accordingly.
  • Validate parentheses pairing: Use a stack to track opening brackets. For each closing bracket, check if it matches the top of the stack. If not, the string is invalid. Ensure the stack is empty at the end.

Complexity:

  • Merge two sorted lists: Time O(n + m), Space O(n + m), where n and m are the lengths of A and B.
  • Max profit from one stock transaction: Time O(n), Space O(1), where n is the number of prices.
  • Validate parentheses pairing: Time O(n), Space O(n), where n is the length of the string.
CodingEasy

11. Design a simple class structure for a Library system that can manage Books and Members.

The full question

Design a simple class structure for a Library system that can manage Books and Members. Implement methods to add a book, add a member, and check out a book to a member.

Model answer

class Book {
  constructor(title, author, isbn) {
    this.title = title;
    this.author = author;
    this.isbn = isbn;
    this.isAvailable = true; // Indicates if the book is available for checkout
  }
}

class Member {
  constructor(name, memberId) {
    this.name = name;
    this.memberId = memberId;
    this.checkedOutBooks = []; // List of books checked out by the member
  }

  // Method to check out a book
  checkoutBook(book) {
    if (book.isAvailable) {
      book.isAvailable = false;
      this.checkedOutBooks.push(book);
      console.log(`${this.name} checked out "${book.title}"`);
    } else {
      console.log(`Sorry, "${book.title}" is currently unavailable.`);
    }
  }
}

class Library {
  constructor() {
    this.books = []; // Collection of books in the library
    this.members = []; // Collection of library members
  }

  // Method to add a new book to the library
  addBook(title, author, isbn) {
    const newBook = new Book(title, author, isbn);
    this.books.push(newBook);
    console.log(`Book "${title}" added to the library.`);
  }

  // Method to add a new member to the library
  addMember(name, memberId) {
    const newMember = new Member(name, memberId);
    this.members.push(newMember);
    console.log(`Member "${name}" added to the library.`);
  }

  // Method to find a book by ISBN
  findBook(isbn) {
    return this.books.find(book => book.isbn === isbn);
  }

  // Method to find a member by ID
  findMember(memberId) {
    return this.members.find(member => member.memberId === memberId);
  }

  // Method to check out a book to a member
  checkoutBookToMember(isbn, memberId) {
    const book = this.findBook(isbn);
    const member = this.findMember(memberId);
    if (book && member) {
      member.checkoutBook(book);
    } else {
      console.log('Book or Member not found.');
    }
  }
}

// Example usage:
const library = new Library();
library.addBook('The Great Gatsby', 'F. Scott Fitzgerald', '123456789');
library.addMember('Alice', 'M001');
library.checkoutBookToMember('123456789', 'M001');
  • Approach:
  • Classes: Define Book, Member, and Library classes to encapsulate related data and behavior.
  • Book Management: Use a Book class with properties like title, author, isbn, and isAvailable.
  • Member Management: Use a Member class to manage member details and their checked-out books.
  • Library Operations: Implement addBook, addMember, and checkoutBookToMember methods in the Library class.
  • Complexity:
  • Time: Adding books and members is O(1). Checking out a book involves finding a book and a member, each O(n) in the worst case.
  • Space: O(n) for storing books and members, where n is the number of books/members.
CodingEasyData ScientistTechnical Screen

12. Three bags contain red (r) and green (g) balls: Bag A: 4 r, 6 g Bag B: 6 r, 4 g Bag C: 3 r, 7 g Process: pick a bag uniformly at random, then draw…

The full question

Three bags contain red (r) and green (g) balls:

  • Bag A: 4 r, 6 g
  • Bag B: 6 r, 4 g
  • Bag C: 3 r, 7 g

Process: pick a bag uniformly at random, then draw one ball from that bag. Given the drawn ball is red, compute the posterior probability that it came from Bag B, i.e., P(B | red). Show all steps using Bayes’ rule. Follow-up: if instead the bag is chosen with probabilities P(A)=0.2, P(B)=0.5, P(C)=0.3, recompute P(B | red) and explain the intuition for how the prior changes the result.

Model answer

# Given data:
# Bag A: 4 red, 6 green
# Bag B: 6 red, 4 green
# Bag C: 3 red, 7 green

# Step 1: Calculate the probability of drawing a red ball from each bag.
p_red_given_A = 4 / (4 + 6)  # Probability of red from Bag A
p_red_given_B = 6 / (6 + 4)  # Probability of red from Bag B
p_red_given_C = 3 / (3 + 7)  # Probability of red from Bag C

# Step 2: Calculate the total probability of drawing a red ball (P(red)).
p_red = (1/3) * p_red_given_A + (1/3) * p_red_given_B + (1/3) * p_red_given_C

# Step 3: Calculate P(B | red) using Bayes' Theorem.
# P(B | red) = (P(red | B) * P(B)) / P(red)
p_B_given_red = p_red_given_B * (1/3) / p_red

# Follow-up: If the bags are chosen with different probabilities:
p_A = 0.2
p_B = 0.5
p_C = 0.3

# Recalculate P(red) with new probabilities.
p_red_new = p_A * p_red_given_A + p_B * p_red_given_B + p_C * p_red_given_C

# Recalculate P(B | red) with new prior probabilities.
p_B_given_red_new = p_red_given_B * p_B / p_red_new

# Output results
p_B_given_red, p_B_given_red_new
  • Approach:
  • Use Bayes' Theorem to compute the posterior probability \( P(B | \text{red}) \).
  • Calculate the probability of drawing a red ball from each bag.
  • Compute the total probability of drawing a red ball.
  • Adjust the calculation for different prior probabilities of selecting each bag.
  • Complexity:
  • Time: \( O(1) \), as the calculations involve a constant number of operations.
  • Space: \( O(1) \), as no additional data structures are used.

Explanation:

  • Initial Calculation:
  • Each bag is equally likely to be chosen, so the prior probability for each is \( \frac{1}{3} \).
  • Compute the probability of drawing a red ball from each bag.
  • Use these probabilities to find the total probability of drawing a red ball.
  • Apply Bayes' Theorem to find \( P(B | \text{red}) \).
  • Follow-up:
  • Change the prior probabilities to \( P(A) = 0.2 \), \( P(B) = 0.5 \), \( P(C) = 0.3 \).
  • Recalculate the total probability of drawing a red ball with these new priors.
  • Use Bayes' Theorem again to find the updated \( P(B | \text{red}) \).
  • Intuition:
  • The prior probabilities affect the posterior probability significantly. A higher prior probability for Bag B increases \( P(B | \text{red}) \), reflecting greater initial confidence in choosing Bag B.
CodingEasySoftware EngineerTechnical Screen

13. Implement a local URL shortener that runs on a single machine.

The full question

Implement a local URL shortener that runs on a single machine.

Design a small library or service with two core operations:

  • shorten(long_url) -> short_code or short_url
  • resolve(short_code) -> original_url

Requirements:

  1. Validate that the input is a well-formed URL and reject invalid input.
  2. Generate a short code using a hash-based or similar encoding strategy.
  3. Handle hash collisions correctly so that no original URL mapping is lost.
  4. Store the mapping between short codes and original URLs in memory using a hash map.
  5. Persist the in-memory mapping to a JSON file, and reload that file when the program starts again.
  6. If the same original URL is shortened multiple times, return the same shortened result instead of creating duplicates.
  7. Handle edge cases such as extremely long URLs.
  8. Write tests for invalid input, collision handling, duplicate requests, and long URLs, with strong line and branch coverage.
  9. Be prepared to discuss how memory usage grows with the number of stored URLs and what changes you would make if the data no longer fits comfortably on one machine.

Assume this is a local-only implementation. Do not introduce external systems such as Redis or a database unless you can justify why they are necessary.

Model answer

const fs = require('fs');
const crypto = require('crypto');

class URLShortener {
  constructor(filePath = 'url_mappings.json') {
    this.filePath = filePath;
    this.urlMap = new Map();
    this.loadMappings();
  }

  // Validate URL
  isValidURL(url) {
    try {
      new URL(url);
      return true;
    } catch (_) {
      return false;
    }
  }

  // Shorten a long URL
  shorten(longUrl) {
    if (!this.isValidURL(longUrl)) {
      throw new Error('Invalid URL');
    }

    // Check if URL is already shortened
    for (let [shortCode, originalUrl] of this.urlMap.entries()) {
      if (originalUrl === longUrl) {
        return shortCode;
      }
    }

    // Generate a unique short code
    let shortCode;
    do {
      shortCode = crypto.randomBytes(4).toString('hex');
    } while (this.urlMap.has(shortCode));

    // Store the mapping
    this.urlMap.set(shortCode, longUrl);
    this.saveMappings();

    return shortCode;
  }

  // Resolve a short code to the original URL
  resolve(shortCode) {
    return this.urlMap.get(shortCode) || null;
  }

  // Save mappings to a JSON file
  saveMappings() {
    const data = JSON.stringify(Object.fromEntries(this.urlMap));
    fs.writeFileSync(this.filePath, data);
  }

  // Load mappings from a JSON file
  loadMappings() {
    if (fs.existsSync(this.filePath)) {
      const data = fs.readFileSync(this.filePath);
      const entries = JSON.parse(data);
      this.urlMap = new Map(Object.entries(entries));
    }
  }
}

// Example usage
const shortener = new URLShortener();
const shortCode = shortener.shorten('https://www.example.com');
console.log(shortCode);
console.log(shortener.resolve(shortCode));
  • Approach:
  • Validate URLs using the URL constructor to ensure they are well-formed.
  • Use a Map to store mappings between short codes and original URLs, ensuring O(1) access time.
  • Generate short codes using crypto.randomBytes to ensure uniqueness and handle collisions.
  • Persist mappings to a JSON file for durability across program restarts.
  • Reload mappings from the JSON file on startup to maintain state.
  • Complexity:
  • Time: O(1) for both shorten and resolve operations due to hash map usage.
  • Space: O(n) where n is the number of unique URLs stored, as each URL and its mapping are stored in memory.
CodingEasyData ScientistTechnical Screen

14. You are given a dataset (X \in \mathbb{R}^{n \times d}) and an integer (k).

The full question

You are given a dataset (X \in \mathbb{R}^{n \times d}) and an integer (k).

  1. Explain K-means: what objective it optimizes and the alternating optimization procedure.
  2. Implement K-means (Lloyd’s algorithm):
  • Initialize (k) centroids.
  • Repeat until convergence / max iterations:
  • Assign each point to its nearest centroid.
  • Recompute each centroid as the mean of points assigned to it.
  • Return final centroids and assignments.
  1. Improve initialization: describe and implement a better initialization strategy than random init (i.e., K-means++).

Clarify how you would handle:

  • Empty clusters
  • Stopping criteria
  • Time complexity

---

Part B — Multi-agent trajectory prediction (Waymo-like)

You are building a model to predict the next 2 timestamps of a target agent (e.g., another car near the ego vehicle). For each training example you have:

  • Past trajectory history for the target agent for (T) steps: ((x_t, y_t)) for (t=1..T)
  • Past trajectories for nearby agents (variable number (M))
  • Map / environment context (e.g., lane polylines, traffic signals), optionally rasterized or vectorized
  • Ground-truth future trajectory for the target agent for the next 2 steps

Questions

  1. Modeling: Propose an ML approach to predict the next 2 positions. Specify:
  • Input representation (agent features, relative coordinates, map encoding)
  • Architecture (e.g., RNN/Transformer, GNN over agents, encoder-decoder)
  • Output parameterization (deterministic points vs probabilistic distribution; multimodal vs unimodal)
  • Loss function(s) and evaluation metrics (e.g., ADE/FDE, NLL)
  1. Multi-head attention (MHA): Explain what MHA is doing in this setting and why

Model answer

import numpy as np

def kmeans(X, k, max_iters=100, tol=1e-4):
    # Step 1: Initialize k centroids randomly from the dataset
    n_samples, n_features = X.shape
    centroids = X[np.random.choice(n_samples, k, replace=False)]

    for _ in range(max_iters):
        # Step 2: Assign each point to the nearest centroid
        distances = np.linalg.norm(X[:, np.newaxis] - centroids, axis=2)
        labels = np.argmin(distances, axis=1)

        # Step 3: Recompute centroids as the mean of assigned points
        new_centroids = np.array([X[labels == i].mean(axis=0) for i in range(k)])

        # Check for convergence (if centroids do not change significantly)
        if np.all(np.linalg.norm(new_centroids - centroids, axis=1) < tol):
            break

        centroids = new_centroids

    return centroids, labels

def kmeans_plus_plus(X, k):
    n_samples, n_features = X.shape
    centroids = np.empty((k, n_features))
    
    # Initialize the first centroid randomly
    centroids[0] = X[np.random.choice(n_samples)]
    
    # Initialize the remaining centroids
    for i in range(1, k):
        distances = np.min(np.linalg.norm(X[:, np.newaxis] - centroids[:i], axis=2), axis=1)
        probabilities = distances / np.sum(distances)
        centroids[i] = X[np.random.choice(n_samples, p=probabilities)]

    return centroids

# Example usage
X = np.random.rand(100, 2)  # Example dataset
k = 3
initial_centroids = kmeans_plus_plus(X, k)
final_centroids, assignments = kmeans(X, k)
  • K-means Objective: K-means aims to minimize the variance within each cluster, effectively minimizing the sum of squared distances between data points and their respective cluster centroids.
  • Lloyd’s Algorithm: 1. Initialize k centroids randomly. 2. Assign each data point to the nearest centroid. 3. Update centroids by calculating the mean of all points assigned to each centroid. 4. Repeat steps 2 and 3 until convergence or a maximum number of iterations is reached.
  • Improved Initialization (K-means++):
  • Select the first centroid randomly.
  • For each subsequent centroid, choose a data point with a probability proportional to its distance squared from the nearest existing centroid.
  • This helps in spreading out the initial centroids, leading to faster convergence and better clustering results.
  • Handling Empty Clusters: If a cluster becomes empty, reinitialize its centroid to a random data point.
  • Stopping Criteria: Convergence is typically determined by checking if the centroids do not change significantly between iterations.

Complexity:

  • Time: O(n k t * d), where n is the number of points, k is the number of clusters, t is the number of iterations, and d is the dimensionality.
  • Space: O(n d) for storing the dataset and O(k d) for the centroids.
CodingEasySoftware EngineerTechnical Screen

15. You are given four independent coding tasks.

The full question

You are given four independent coding tasks. For each task, implement the required function.

---

Problem 1: Zigzag level-order traversal of a binary tree

Given the root of a binary tree, return the node values level by level, but alternate the traversal direction each level:

  • Level 0 (root level): left → right
  • Level 1: right → left
  • Level 2: left → right
  • … and so on.

Input: root (binary tree node)

Output: A list of lists, where each inner list contains the values for one level in the required order.

Example:

  • Tree: 1,2,3,4,5,null,6
  • Output: [[1],[3,2],[4,5,6]]

Constraints: number of nodes up to ~10^4.

---

Problem 2: Longest palindromic subsequence length

Given a string s, return the length of the longest subsequence of s that is a palindrome.

  • A subsequence can delete characters without changing the relative order of the remaining characters.

Input: string s

Output: integer length

Example:

  • Input: s = "bbbab"
  • Output: 4 (one answer is subsequence "bbbb")

Constraints: 1 <= len(s) <= 1000.

---

Problem 3: Integer square root (floor)

Given a non-negative integer x, compute and return ⌊sqrt(x)⌋ (the integer part of the square root).

Input: integer x >= 0

Output: integer r such that r*r <= x < (r+1)*(r+1)

Example:

  • Input: x = 8
  • Output: 2

Constraints: 0 <= x <= 2^31 - 1.

---

Problem 4: Spreadsheet column label to number

In a spreadsheet, columns are labeled A, B, ..., Z, AA, AB, ..., AZ, BA, ....

Given a column label string col consisting of uppercase English letters, return its 1-indexed

Model answer

// Problem 1: Zigzag level-order traversal of a binary tree
function zigzagLevelOrder(root) {
    if (!root) return [];
    
    const result = [];
    const queue = [root];
    let leftToRight = true;
    
    while (queue.length > 0) {
        const levelSize = queue.length;
        const currentLevel = [];
        
        for (let i = 0; i < levelSize; i++) {
            const node = queue.shift();
            if (leftToRight) {
                currentLevel.push(node.val);
            } else {
                currentLevel.unshift(node.val);
            }
            
            if (node.left) queue.push(node.left);
            if (node.right) queue.push(node.right);
        }
        
        result.push(currentLevel);
        leftToRight = !leftToRight;
    }
    
    return result;
}

// Problem 2: Longest palindromic subsequence length
function longestPalindromeSubseq(s) {
    const n = s.length;
    const dp = Array.from({ length: n }, () => Array(n).fill(0));
    
    for (let i = n - 1; i >= 0; i--) {
        dp[i][i] = 1;
        for (let j = i + 1; j < n; j++) {
            if (s[i] === s[j]) {
                dp[i][j] = dp[i + 1][j - 1] + 2;
            } else {
                dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
            }
        }
    }
    
    return dp[0][n - 1];
}

// Problem 3: Integer square root (floor)
function mySqrt(x) {
    if (x < 2) return x;
    
    let left = 1, right = Math.floor(x / 2);
    
    while (left <= right) {
        const mid = Math.floor((left + right) / 2);
        const midSquared = mid * mid;
        
        if (midSquared === x) return mid;
        if (midSquared < x) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return right;
}

// Problem 4: Spreadsheet column label to number
function titleToNumber(col) {
    let result = 0;
    
    for (let i = 0; i < col.length; i++) {
        result = result * 26 + (col.charCodeAt(i) - 'A'.charCodeAt(0) + 1);
    }
    
    return result;
}
  • Problem 1: Use a queue for level-order traversal, alternating between pushing and unshifting node values based on the current direction.
  • Problem 2: Use dynamic programming to build a table dp[i][j] representing the longest palindromic subsequence between indices i and j.
  • Problem 3: Use binary search to find the integer square root by narrowing down the range based on the midpoint square.
  • Problem 4: Convert the column label to a number by iterating through the string and using a base-26 calculation.

Complexity:

  • Problem 1: Time/Space: O(n), where n is the number of nodes.
  • Problem 2: Time/Space: O(n^2), where n is the length of the string.
  • Problem 3: Time: O(log x), Space: O(1).
  • Problem 4: Time: O(m), Space: O(1), where m is the length of the column string.
CodingEasySoftware EngineerTechnical Screen

16. Implement an iterator over a mutable array of integer arrays.

The full question

Implement an iterator over a mutable array of integer arrays. Traversal goes left to right within each inner array and top to bottom across the outer array. Empty inner arrays are skipped.

Your class must expose:

hasNext() -> bool
next() -> int
remove() -> void

remove() deletes from the original nested-list object the element returned by the most recent successful next().

Required Behavior

  • hasNext() reports whether another integer remains and may be called repeatedly without advancing the iterator.
  • next() returns the next integer. It raises an iteration-exhausted exception when no value remains.
  • remove() is legal exactly once after each successful next().
  • Calling remove() before next(), or calling it twice after the same next(), raises an illegal-state exception.
  • Removing an element must not cause the following element in the same row to be skipped.
  • Mutations made by remove() must be visible through the original nested list supplied to the constructor.

Example

data = [[], [1, 2, 3], [4, 5], [], [6]]
it = NestedIterator(data)

it.hasNext() -> true
it.next() -> 1
it.next() -> 2
it.remove()

data -> [[], [1, 3], [4, 5], [], [6]]
it.next() -> 3

Constraints

  • The outer and inner arrays exist for the lifetime of the iterator.
  • Clients do not mutate the nested arrays except through this iterator's remove().
  • Values may repeat; position, not value, identifies what must be removed.
  • Empty outer input is valid.

Model answer

class NestedIterator {
    constructor(nestedList) {
        this.nestedList = nestedList;
        this.outerIndex = 0;
        this.innerIndex = 0;
        this.lastReturned = null;
        this.advanceToNext();
    }

    // Advance to the next available integer
    advanceToNext() {
        while (this.outerIndex < this.nestedList.length) {
            if (this.innerIndex < this.nestedList[this.outerIndex].length) {
                return;
            }
            this.outerIndex++;
            this.innerIndex = 0;
        }
    }

    hasNext() {
        return this.outerIndex < this.nestedList.length;
    }

    next() {
        if (!this.hasNext()) {
            throw new Error("No more elements");
        }
        this.lastReturned = [this.outerIndex, this.innerIndex];
        const result = this.nestedList[this.outerIndex][this.innerIndex];
        this.innerIndex++;
        this.advanceToNext();
        return result;
    }

    remove() {
        if (this.lastReturned === null) {
            throw new Error("Illegal state");
        }
        const [outer, inner] = this.lastReturned;
        this.nestedList[outer].splice(inner, 1);
        if (inner < this.innerIndex) {
            this.innerIndex--;
        }
        this.lastReturned = null;
    }
}

// Example usage:
const data = [[], [1, 2, 3], [4, 5], [], [6]];
const it = new NestedIterator(data);
console.log(it.hasNext()); // true
console.log(it.next());    // 1
console.log(it.next());    // 2
it.remove();
console.log(data);         // [[], [1, 3], [4, 5], [], [6]]
console.log(it.next());    // 3
  • Approach:
  • Maintain indices outerIndex and innerIndex to track the current position in the nested list.
  • Use advanceToNext() to skip empty inner arrays and position the iterator at the next available integer.
  • hasNext() checks if there are more integers to iterate over.
  • next() returns the current integer and advances the iterator.
  • remove() deletes the last returned integer and adjusts indices accordingly.

Complexity:

  • Time: O(1) for hasNext(), next(), and remove() in typical cases, though advanceToNext() may take O(n) in the worst case when skipping empty arrays.
  • Space: O(1), as we use a fixed amount of extra space.
CodingEasyData EngineerTechnical Screen

17. You are asked to solve the following two coding problems.

The full question

You are asked to solve the following two coding problems.

  1. Count unique Morse code translations

You are given an array of lowercase English words, words.

Each letter maps to standard Morse code as follows:

  • a -> .-
  • b -> -...
  • c -> -.-.
  • d -> -..
  • e -> .
  • f -> ..-.
  • g -> --.
  • h -> ....
  • i -> ..
  • j -> .---
  • k -> -.-
  • l -> .-..
  • m -> --
  • n -> -.
  • o -> ---
  • p -> .--.
  • q -> --.-
  • r -> .-.
  • s -> ...
  • t -> -
  • u -> ..-
  • v -> ...-
  • w -> .--
  • x -> -..-
  • y -> -.--
  • z -> --..

The translation of a word is the concatenation of the Morse codes of its letters. For example, "cab" becomes "-.-..--..." because c -> -.-., a -> .-, and b -> -....

Return the number of distinct word translations among all words in the input array.

  1. Return all valid word-break sentences

You are given a string s and a dictionary of words wordDict.

Insert spaces into s to form all possible sentences such that every token is a word in wordDict. The same dictionary word may be reused multiple times.

Return all valid sentences in any order.

Example:

  • s = "catsanddog"
  • wordDict = ["cat", "cats", "and", "sand", "dog"]
  • Output: ["cats and dog", "cat sand dog"]

Design correct and efficient algorithms for both problems, and be prepared to discuss time and space complexity.

Model answer

// Problem 1: Count unique Morse code translations

function uniqueMorseRepresentations(words) {
    const morseCodeMap = [
        ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
        "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."
    ];
    
    const seen = new Set();
    
    for (let word of words) {
        let morseTranslation = '';
        for (let char of word) {
            morseTranslation += morseCodeMap[char.charCodeAt(0) - 'a'.charCodeAt(0)];
        }
        seen.add(morseTranslation);
    }
    
    return seen.size;
}

// Problem 2: Return all valid word-break sentences

function wordBreak(s, wordDict) {
    const wordSet = new Set(wordDict);
    const memo = new Map();
    
    function backtrack(start) {
        if (memo.has(start)) return memo.get(start);
        if (start === s.length) return [''];
        
        const sentences = [];
        
        for (let end = start + 1; end <= s.length; end++) {
            const word = s.substring(start, end);
            if (wordSet.has(word)) {
                const restOfSentences = backtrack(end);
                for (let sentence of restOfSentences) {
                    sentences.push(word + (sentence ? ' ' + sentence : ''));
                }
            }
        }
        
        memo.set(start, sentences);
        return sentences;
    }
    
    return backtrack(0);
}

// Approach for Problem 1:
// - Create a map of Morse code for each letter.
// - Use a set to store unique Morse translations of words.
// - For each word, translate it to Morse code and add to the set.
// - Return the size of the set as the count of unique translations.

// Approach for Problem 2:
// - Use a backtracking approach with memoization to explore all possible sentences.
// - For each starting index, check all substrings if they are in the word dictionary.
// - Recursively find valid sentences for the remaining string.
// - Memoize results to avoid redundant calculations.

// Complexity:
// - Problem 1: Time O(n * m), Space O(n), where n is the number of words and m is the average length of a word.
// - Problem 2: Time O(n^3), Space O(n^3), where n is the length of the string `s`.
CodingEasySoftware EngineerTechnical Screen

18. You are given a binary tree node definition: TreeNode { int val; TreeNode left; TreeNode right; } Answer the following two algorithmic questions.

The full question

You are given a binary tree node definition:

  • TreeNode { int val; TreeNode left; TreeNode right; }

Answer the following two algorithmic questions.

1) Reverse-print a binary tree

Implement a function that returns the values of the tree level by level from bottom to top.

  • Input: root (possibly null)
  • Output: a list of levels, where each level is a list of node values
  • Order requirement: within each level, nodes are listed from left to right, but the levels are returned in reverse (deepest level first).

Example

Tree:

  • 1
  • left: 2 (children: 4, 5)
  • right: 3 (right child: 6)

Output: [[4,5,6],[2,3],[1]]

2) Longest palindromic subsequence (LPS)

Given a string s, return the length of the longest subsequence of s that is a palindrome.

  • A subsequence keeps relative order but may delete characters.
  • Input: string s
  • Output: integer length

Example

  • Input: "bbbab"
  • Output: 4 (one LPS is "bbbb")

Constraints (assume typical interview constraints)

  • 0 <= number_of_nodes <= 10^5 for the tree
  • 1 <= |s| <= 2000 for LPS

Model answer

// Function to reverse-print a binary tree level by level from bottom to top
function reverseLevelOrder(root) {
    if (!root) return [];

    const queue = [root];
    const result = [];

    while (queue.length > 0) {
        const levelSize = queue.length;
        const currentLevel = [];

        for (let i = 0; i < levelSize; i++) {
            const node = queue.shift();
            currentLevel.push(node.val);

            if (node.left) queue.push(node.left);
            if (node.right) queue.push(node.right);
        }

        // Insert the current level at the beginning of the result array
        result.unshift(currentLevel);
    }

    return result;
}

// Function to find the length of the longest palindromic subsequence
function longestPalindromeSubseq(s) {
    const n = s.length;
    const dp = Array.from({ length: n }, () => Array(n).fill(0));

    // Each character is a palindrome of length 1
    for (let i = 0; i < n; i++) {
        dp[i][i] = 1;
    }

    // Build the dp table
    for (let length = 2; length <= n; length++) {
        for (let i = 0; i <= n - length; i++) {
            const j = i + length - 1;
            if (s[i] === s[j]) {
                dp[i][j] = dp[i + 1][j - 1] + 2;
            } else {
                dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
            }
        }
    }

    return dp[0][n - 1];
}
  • Reverse Level Order Traversal:
  • Use a queue to perform a level-order traversal (BFS).
  • For each level, store node values in a temporary list.
  • Insert each level's list at the start of the result list to reverse the order.
  • Longest Palindromic Subsequence:
  • Use dynamic programming to build a table dp where dp[i][j] represents the length of the longest palindromic subsequence in s[i...j].
  • Initialize dp[i][i] to 1 for all i since each character is a palindrome.
  • Fill the table by checking if characters at i and j are equal, and use previously computed values to find the longest subsequence.

Complexity:

  • Reverse Level Order Traversal: Time: O(n), Space: O(n), where n is the number of nodes.
  • Longest Palindromic Subsequence: Time: O(n^2), Space: O(n^2), where n is the length of the string.
CodingEasyData ScientistCoding screen

19. Can you explain how you would use Python lists and dictionaries to manage data in a data science project?

The full question

Can you explain how you would use Python lists and dictionaries to manage data in a data science project? Provide an example of how you might implement these structures.

Model answer

The flow

  1. Clarify inputs & output shape: Identify the data types and structures needed for the project.
  2. Brute force first: Implement a basic solution using Python lists and dictionaries.
  3. Optimize: Improve the efficiency of data manipulation and retrieval.
  4. State complexity: Analyze the time and space complexity of the solution.
  5. Test the edges: Ensure the solution handles edge cases and large datasets effectively.

The answer

1. Clarify inputs & output shape

  • For a data science project, you often deal with datasets that can be represented as lists of dictionaries, where each dictionary represents a data record with key-value pairs.
  • Example: A dataset of employees where each employee has attributes like name, age, and department.

2. Brute force first

  • Start by using lists to store multiple records and dictionaries to store attributes of each record.
# Example dataset of employees
employees = [
    {"name": "Alice", "age": 30, "department": "HR"},
    {"name": "Bob", "age": 25, "department": "Engineering"},
    {"name": "Charlie", "age": 35, "department": "Marketing"}
]

# Function to find employees in a specific department
def find_employees_by_department(employees, department):
    return [emp for emp in employees if emp["department"] == department]

# Example usage
hr_employees = find_employees_by_department(employees, "HR")
print(hr_employees)
  • Approach: Use a list comprehension to filter employees by department.

3. Optimize

  • If frequent lookups by department are needed, consider restructuring the data for faster access.
# Optimized data structure using a dictionary
employees_by_department = {
    "HR": [{"name": "Alice", "age": 30}],
    "Engineering": [{"name": "Bob", "age": 25}],
    "Marketing": [{"name": "Charlie", "age": 35}]
}

# Function to get employees by department
def get_employees_by_department(department):
    return employees_by_department.get(department, [])

# Example usage
hr_employees = get_employees_by_department("HR")
print(hr_employees)
  • Approach: Use a dictionary to map departments to lists of employees, improving lookup time.

4. State complexity

  • Time Complexity: Initial brute force approach has $O(n)$ time complexity for lookups, where $n$ is the number of employees. Optimized approach reduces lookup time to $O(1)$.
  • Space Complexity: Both approaches use $O(n)$ space, but the optimized approach may require additional space for the dictionary.

5. Test the edges

  • Test with an empty list, a single record, and a large dataset to ensure robustness.

Why this works

  • Interviewer is testing: Ability to use basic data structures effectively and optimize for efficiency.
  • Sanity check: Ensures the candidate understands how to structure data for common operations.
  • Weak answers: May overlook the need for optimization or fail to handle edge cases, leading to inefficient or incorrect solutions.
CodingEasySoftware EngineerTechnical Screen

20. You are building a rover navigation simulator.

The full question

You are building a rover navigation simulator. The exercise is incremental: you implement a single rover on a 2D grid, then extend the design to many rovers sharing one map, then generalize the model to a 3D map. This is a pair-programming/live-coding question — the interviewer cares as much about how cleanly your design absorbs each new requirement as about the final output, so prefer small, well-named abstractions over one large function.

Model answer

class Rover {
  constructor(x, y, direction) {
    this.x = x; // X-coordinate on the grid
    this.y = y; // Y-coordinate on the grid
    this.direction = direction; // Current direction the rover is facing
    this.directions = ['N', 'E', 'S', 'W']; // Possible directions
  }

  // Method to turn the rover left
  turnLeft() {
    const currentIndex = this.directions.indexOf(this.direction);
    this.direction = this.directions[(currentIndex + 3) % 4];
  }

  // Method to turn the rover right
  turnRight() {
    const currentIndex = this.directions.indexOf(this.direction);
    this.direction = this.directions[(currentIndex + 1) % 4];
  }

  // Method to move the rover forward
  moveForward() {
    switch (this.direction) {
      case 'N':
        this.y += 1;
        break;
      case 'E':
        this.x += 1;
        break;
      case 'S':
        this.y -= 1;
        break;
      case 'W':
        this.x -= 1;
        break;
    }
  }

  // Method to execute a series of commands
  executeCommands(commands) {
    for (let command of commands) {
      switch (command) {
        case 'L':
          this.turnLeft();
          break;
        case 'R':
          this.turnRight();
          break;
        case 'M':
          this.moveForward();
          break;
      }
    }
  }

  // Method to get the current position and direction of the rover
  getPosition() {
    return `${this.x} ${this.y} ${this.direction}`;
  }
}

// Example usage:
const rover = new Rover(0, 0, 'N');
rover.executeCommands('LMLMLMLMM');
console.log(rover.getPosition()); // Output: "0 1 N"
  • Approach:
  • Define a Rover class with properties for position (x, y) and direction.
  • Implement methods to turn the rover left or right and move it forward.
  • Use an array to manage direction changes, allowing easy calculation of new directions.
  • Provide a method to execute a sequence of commands and update the rover's state.
  • Complexity:
  • Time: O(n) for executing commands, where n is the number of commands.
  • Space: O(1), as the space used does not scale with input size.

Practice these out loud, don't memorise them

Reading an answer is not the same as being able to give one under pressure. ChannelPulse plays the interviewer, asks the follow-ups, and scores each answer with feedback and a model answer so you can hear the gap between what you said and what lands.

Get ChannelPulse Browse all questions