For trading and quant roles: algorithms, data structures, and trading-adjacent coding problems — with full solutions.
- Free preview
- Full worked solutions
- Medium
- Hard
Trading and quant firms screen for programming ability alongside math, though how far it goes depends on the role — a developer or quant-dev seat is tested far more heavily than a trading seat. Expect questions on algorithms, data structures, order-book manipulation, backtesting logic, and low-latency edge cases. The examples below sit at the difficulty these interviews tend to ask.
Sample Questions & Solutions
Each question is a real interview problem. Try it yourself first, the full solution is revealed below.
Longest Common Subsequence (LCS)
Mediumdef find_lcs(seq1: list, seq2: list) -> list:
pass
Inputs
- seq1: A list of integers representing the first sequence.
- seq2: A list of integers representing the second sequence.
Return a list of integers representing the Longest Common Subsequence of seq1 and seq2.
Example
Given the sequences seq1 = [1, 2, 3, 4, 5] and seq2 = [6, 7, 1, 2, 8], your function should return [1, 2].
Show solution
# Implementing the Longest Common Subsequence (LCS) using Dynamic Programming
def find_lcs(seq1, seq2):
n = len(seq1)
m = len(seq2)
# Initialize a DP table with zeros
dp = [[0] * (m + 1) for _ in range(n + 1)]
# DP step: fill in the table
for i in range(1, n + 1):
for j in range(1, m + 1):
if seq1[i-1] == seq2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
# Reconstruct the LCS
lcs = []
i, j = n, m
while i > 0 and j > 0:
if seq1[i-1] == seq2[j-1]:
lcs.append(seq1[i-1])
i -= 1
j -= 1
elif dp[i-1][j] > dp[i][j-1]:
i -= 1
else:
j -= 1
return lcs[::-1] # Reverse the LCS to get it in the correct order
# Test the function
seq1 = [1, 2, 3, 4, 5]
seq2 = [6, 7, 1, 2, 8]
print(find_lcs(seq1, seq2)) # Should return [1, 2]
Dijkstra's Algorithm #1
HardObjective
You're given a weighted graph, represented as an adjacency list. Your task is to find the shortest path between two specified nodes using Dijkstra's algorithm.
def dijkstra_shortest_path(graph: dict, start: str, end: str) -> list:
pass
Inputs
- graph: A dictionary where the keys are node names (as strings), and the values are dictionaries containing adjacent nodes and the weights of the edges to those nodes.
- start: A string representing the starting node.
- end: A string representing the ending node.
Return a list of nodes that form the shortest path from start to end. If there's no path, return 'None'.
Example
For the graph defined as:
graph = {
'A': {'B': 1, 'C': 4},
'B': {'A': 1, 'C': 2, 'D': 5},
'C': {'A': 4, 'B': 2, 'D': 1},
'D': {'B': 5, 'C': 1}
}
The function call dijkstra_shortest_path(graph, 'A', 'D') should return ['A', 'B', 'C', 'D'] as this is the shortest path from 'A' to 'D'.
- The distance from 'A' to 'B' is 1
- The distance from 'B' to 'C' is 2
- The distance from 'C' to 'D' is 1
For more information, take a look at this video.
Notes
Write an efficient algorithm. A graph can have over 1000 nodes and the edge weights are positive integers.
Show solution
import heapq
# Implement Dijkstra's algorithm for shortest path
def dijkstra_shortest_path(graph: dict, start: str, end: str) -> list:
# Initialize distance dictionary with infinite distance for all nodes except the start node
distance = {node: float('inf') for node in graph}
distance[start] = 0
# Priority queue to keep track of nodes to be explored (distance, node)
pq = [(0, start)]
# Dictionary to keep track of predecessors for each node
predecessors = {node: None for node in graph}
while pq:
# Get the node with the smallest distance from the priority queue
current_distance, current_node = heapq.heappop(pq)
# If this node is the end node, we are done
if current_node == end:
path = []
while current_node is not None:
path.insert(0, current_node)
current_node = predecessors[current_node]
return path
# Explore neighbors
for neighbor, weight in graph[current_node].items():
new_distance = current_distance + weight
# Update distance if a shorter path is found
if new_distance < distance[neighbor]:
distance[neighbor] = new_distance
predecessors[neighbor] = current_node
heapq.heappush(pq, (new_distance, neighbor))
# If we reach here, there is no path from start to end
return None
# Test the function
test_graph = {
'A': {'B': 1, 'C': 4},
'B': {'A': 1, 'C': 2, 'D': 5},
'C': {'A': 4, 'B': 2, 'D': 1},
'D': {'B': 5, 'C': 1}
}
print(dijkstra_shortest_path(test_graph, 'A', 'D')) # Should return ['A', 'B', 'C', 'D']
Solution 2: Without importing packages
# Implement Dijkstra's algorithm for shortest path without external packages
def dijkstra_shortest_path_no_heap(graph: dict, start: str, end: str) -> list:
# Initialize distance dictionary with infinite distance for all nodes except the start node
distance = {node: float('inf') for node in graph}
distance[start] = 0
# Set to keep track of visited nodes
visited = set()
# Dictionary to keep track of predecessors for each node
predecessors = {node: None for node in graph}
# Current node to explore
current_node = start
while current_node is not None:
# Mark the current node as visited
visited.add(current_node)
# If this node is the end node, we are done
if current_node == end:
path = []
while current_node is not None:
path.insert(0, current_node)
current_node = predecessors[current_node]
return path
# Explore neighbors
for neighbor, weight in graph[current_node].items():
new_distance = distance[current_node] + weight
# Update distance if a shorter path is found
if new_distance < distance[neighbor]:
distance[neighbor] = new_distance
predecessors[neighbor] = current_node
# Select the unvisited node with the smallest distance as the next node to visit
candidates = {node: dist for node, dist in distance.items() if node not in visited}
current_node = min(candidates, key=candidates.get, default=None)
# If we reach here, there is no path from start to end
return None
# Test the function
test_graph = {
'A': {'B': 1, 'C': 4},
'B': {'A': 1, 'C': 2, 'D': 5},
'C': {'A': 4, 'B': 2, 'D': 1},
'D': {'B': 5, 'C': 1}
}
print(dijkstra_shortest_path_no_heap(test_graph, 'A', 'D')) # Should return ['A', 'B', 'C', 'D'] Priority Queue Using Heaps
Hardclass PriorityQueue:
def __init__(self):
pass
def insert(self, element: int) -> None:
pass
def delete(self) -> int:
pass
def peek(self) -> int:
pass
def _heapify_up(self) -> None:
pass
def _heapify_down(self) -> None:
pass
Methods
- __init__(): Initializes the Priority Queue.
- insert(element: int): Inserts an element into the Priority Queue while maintaining the heap property.
- delete(): Removes and returns the element with the highest priority (maximum value for max-heap, minimum value for min-heap).
- peek() -> int: Returns the element with the highest priority without removing it from the Priority Queue.
- _heapify_up(): Called after an insert operation. If the max heap condition is violated due to the newly inserted element, this method adjusts the heap to restore the max heap property.
- _heapify_down(): Called after a delete operation. If the max heap condition is violated due to the swap and removal of the root, this method adjusts the heap to restore the max heap property.
pq = PriorityQueue()
pq.insert(3)
pq.insert(1)
pq.insert(4)
print(pq.peek()) # Should return 4 if max-heap, 1 if min-heap
pq.delete()
print(pq.peek()) # Should return 3 if max-heap, 1 if min-heap
Note
You can assume that the delete() and peek() methods will not be called on an empty Priority Queue.
Show solution
class PriorityQueue:
def __init__(self):
self.heap = []
def insert(self, element):
self.heap.append(element)
self._heapify_up()
def delete(self):
if len(self.heap) == 0:
return None
if len(self.heap) == 1:
return self.heap.pop()
root = self.heap[0]
self.heap[0] = self.heap.pop()
self._heapify_down()
return root
def peek(self):
return self.heap[0] if self.heap else None
def _heapify_up(self):
index = len(self.heap) - 1
while index > 0:
parent_index = (index - 1) // 2
if self.heap[index] > self.heap[parent_index]:
self.heap[index], self.heap[parent_index] = self.heap[parent_index], self.heap[index]
index = parent_index
else:
break
def _heapify_down(self):
index = 0
while index < len(self.heap):
left_child_index = 2 * index + 1
right_child_index = 2 * index + 2
# Find the maximum child index
if left_child_index < len(self.heap) and right_child_index < len(self.heap):
max_child_index = left_child_index if self.heap[left_child_index] > self.heap[right_child_index] else right_child_index
elif left_child_index < len(self.heap):
max_child_index = left_child_index
else:
break
# Swap if the child is greater than the parent
if self.heap[max_child_index] > self.heap[index]:
self.heap[max_child_index], self.heap[index] = self.heap[index], self.heap[max_child_index]
index = max_child_index
else:
break
# Test the Priority Queue implementation
pq = PriorityQueue()
pq.insert(3)
pq.insert(1)
pq.insert(4)
assert pq.peek() == 4 # Should return 4 for max-heap
assert pq.delete() == 4 # Should return 4 for max-heap
assert pq.peek() == 3 # Should return 3 for max-heap
print("Priority Queue implementation is working correctly.") Why practise these
In trading and quant work, ideas eventually have to become working code — a model, a backtest, a quick script to test an idea. These questions check whether you can move between the math, the intuition, and the code without getting stuck on any one of them.
Ready for the full question bank?
You just worked through 3 of our free sample questions. Full access unlocks 500+ interview questions, timed mock OAs, progress tracking, and detailed analytics across every trading firm listed above.