Count Nodes Using Asynchronous Messages
Company: OpenAI
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates skills in distributed systems and asynchronous message-passing, specifically decentralized graph traversal, aggregation, concurrency control, and fault-tolerant coordination across independent nodes.
Part 1: Count Total Reachable Nodes Using Asynchronous Messages
Constraints
- 0 <= number of reachable nodes <= 100000
- The reachable portion from root forms a tree: no cycles and no node has two different parents
- Each child list contains distinct node ids
Examples
Input: (1, {1: [2, 3], 2: [4, 5], 3: [6], 4: [], 5: [], 6: []})
Expected Output: 6
Explanation: All 6 nodes are reachable from the root.
Input: (1, {1: [2], 2: [3], 3: [], 99: [100], 100: []})
Expected Output: 3
Explanation: Nodes 99 and 100 are not reachable from root 1, so they are ignored.
Input: (7, {7: []})
Expected Output: 1
Explanation: A single-node tree has count 1.
Input: (None, {1: [2], 2: []})
Expected Output: 0
Explanation: No root means no traversal.
Hints
- Model two message types: a request that asks a child for its subtree count, and a response that returns that count.
- A node can reply to its parent only after it has received responses from all of its children.
Part 2: Build the Full Topology String Using Asynchronous Messages
Constraints
- 0 <= number of reachable nodes <= 100000
- The reachable portion from root forms a tree
- Child order in each list must be preserved in the output
Examples
Input: (1, {1: [2, 3], 2: [4, 5], 3: [6], 4: [], 5: [], 6: []})
Expected Output: '1(2(4,5),3(6))'
Explanation: Each node wraps the string representations of its children.
Input: (1, {1: [3, 2], 2: [], 3: []})
Expected Output: '1(3,2)'
Explanation: The output must preserve the input child order.
Input: (7, {})
Expected Output: '7'
Explanation: A missing key means node 7 is a leaf.
Input: (1, {1: [2], 2: [], 9: [10], 10: []})
Expected Output: '1(2)'
Explanation: Unreachable nodes 9 and 10 are ignored.
Input: (None, {1: [2], 2: []})
Expected Output: ''
Explanation: No root means no topology string.
Hints
- This is the same bottom-up aggregation pattern as subtree counting, but now each child returns a string instead of an integer.
- Store child results by child id, then rebuild them in the original child-list order when the parent finishes.
Part 3: Reliable Asynchronous Counting with Retries, Request IDs, and Caching
Constraints
- 0 <= number of reachable nodes <= 100000
- The reachable portion from root forms a tree
- 0 <= max_retries <= 50, and all failure counts are non-negative integers
Examples
Input: (1, {1: [2, 3], 2: [4], 3: [], 4: []}, {}, {}, {}, 0)
Expected Output: 4
Explanation: No failures occur, so normal subtree counting works.
Input: (1, {1: [2, 3], 2: [4], 3: [], 4: []}, {(1, 2): 1}, {(4, 2): 1}, {}, 2)
Expected Output: 4
Explanation: The first request to 2 is lost, and the first response from 4 to 2 is lost, but retries succeed.
Input: (1, {1: [2, 3], 2: [], 3: []}, {}, {}, {3: 1}, 1)
Expected Output: 3
Explanation: Node 3 ignores the first delivered request, then succeeds on the retry.
Input: (1, {1: [2], 2: []}, {(1, 2): 2}, {}, {}, 1)
Expected Output: -1
Explanation: Two lost requests require three total attempts, but only two are allowed.
Input: (None, {1: [2], 2: []}, {}, {}, {}, 5)
Expected Output: 0
Explanation: No root means nothing to count.
Hints
- Use the same request id on every retry of the same parent-to-child request.
- Cache only completed results; if a subtree never finishes, a later retry may need to recompute it.
Part 4: Concurrent Traversal Requests with Per-Request State Isolation
Constraints
- 0 <= number of requests <= 10000
- The reachable portion from any requested root forms a tree
- Request ids are unique, and each child list contains distinct node ids
Examples
Input: ({1: [2, 3], 2: [4, 5], 3: [], 4: [], 5: []}, [('A', 1), ('B', 2)])
Expected Output: [('A', 5), ('B', 3)]
Explanation: Request A counts the full tree under 1, while request B independently counts the subtree rooted at 2.
Input: ({1: [2], 2: []}, [('R1', 1), ('R2', 1)])
Expected Output: [('R1', 2), ('R2', 2)]
Explanation: Two concurrent requests can ask for the same root and still must remain independent.
Input: ({10: [11], 11: [], 20: []}, [('X', 10), ('Y', 20)])
Expected Output: [('X', 2), ('Y', 1)]
Explanation: Requests may target unrelated parts of the network.
Input: ({}, [])
Expected Output: []
Explanation: No requests means no work.
Input: ({7: []}, [('N', None), ('S', 7)])
Expected Output: [('N', 0), ('S', 1)]
Explanation: The None-root request returns 0, while a leaf root returns 1.
Hints
- A single pending set per node is not enough. Key the state by both node id and request id.
- This is the same message protocol as one traversal, but now several independent traversals share the same nodes.
Part 5: Local sendAsyncMessage Simulator with End-to-End Async Completion Time
Constraints
- 0 <= number of reachable nodes <= 100000
- All delays are non-negative integers, and missing delays default to 1
- The reachable portion from root forms a tree
Examples
Input: (1, {1: [2, 3], 2: [4]}, {(1, 2): 2, (1, 3): 1, (2, 4): 1}, {(2, 1): 2, (3, 1): 4, (4, 2): 2})
Expected Output: (4, 7)
Explanation: Node 3 responds to the root at time 5. Node 4 responds to node 2 at time 5, then node 2 responds to the root at time 7. The total subtree size is 4.
Input: (1, {1: [2, 3]}, {}, {})
Expected Output: (3, 2)
Explanation: Both children receive the request at time 1 and respond at time 2 using default delays. The root learns the total count 3 at time 2.
Input: (None, {}, {}, {})
Expected Output: (0, 0)
Explanation: If there is no root, there are no nodes to count and no time passes.
Input: (5, {}, {}, {})
Expected Output: (1, 0)
Explanation: A single root node already knows its subtree size immediately at time 0.
Input: (1, {1: [2, 3], 2: [4]}, {(1, 2): 0, (1, 3): 0, (2, 4): 0}, {(4, 2): 0, (2, 1): 1, (3, 1): 0})
Expected Output: (4, 1)
Explanation: Several messages are delivered at time 0, so insertion order matters. Node 2 still needs 1 time unit to send its final response to the root, so the root finishes at time 1.
Input: (10, {10: [20], 20: [30], 99: [100]}, {(10, 20): 3}, {(30, 20): 2})
Expected Output: (3, 7)
Explanation: Only nodes reachable from 10 are counted. Delays are: 10->20 = 3, 20->30 = 1 (default), 30->20 = 2, 20->10 = 1 (default), so the root finishes at time 7 with count 3.
Hints
- Use a min-heap of scheduled events like (time, sequence_number, message).
- A parent's completion time depends on the latest child response delivery, not just on the number of children.