Compute graph distance and impacted services
Company: LinkedIn
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates proficiency in graph algorithms and traversal (shortest-path/BFS and cycle handling), file-path/tree modeling, dependency graph reasoning, and ordering impacts across service producers and consumers.
Part 1: Shortest Distance in an Unweighted Graph
Constraints
- 0 <= number of nodes <= 10^5
- 0 <= total number of edges <= 2 * 10^5
- Node ids are strings
- The graph may contain cycles
- Nodes with no outgoing edges may be omitted from `graph` or mapped to an empty list
Examples
Input: ({'A': ['B', 'C'], 'B': ['D'], 'C': ['D'], 'D': []}, 'A', 'D')
Expected Output: 2
Explanation: There are two shortest paths, `A -> B -> D` and `A -> C -> D`, both with length 2.
Input: ({'A': ['B'], 'B': ['C'], 'C': ['A'], 'D': []}, 'A', 'D')
Expected Output: -1
Explanation: The graph has a cycle among `A, B, C`, but `D` is unreachable.
Input: ({}, 'X', 'X')
Expected Output: 0
Explanation: The start and target are the same node, so the distance is 0.
Input: ({'A': ['B'], 'B': []}, 'A', 'C')
Expected Output: -1
Explanation: Node `C` is never reached from `A`.
Hints
- In an unweighted graph, breadth-first search explores nodes in increasing distance order.
- Use a visited set so cycles do not cause infinite loops.
Part 2: Directly Impacted Services by Exact Deleted Paths
Constraints
- 0 <= number of services <= 10^5
- 0 <= len(deleted_list) <= 10^5
- Each service name is unique
- Each service contains both `read_from` and `write_to` keys
- Paths are UNIX-like absolute strings
Examples
Input: ({'svcA': {'read_from': '/path123', 'write_to': '/path456'}, 'svcB': {'read_from': '/path456', 'write_to': '/path789'}}, ['/path123'])
Expected Output: ['svcA']
Explanation: Only `svcA` reads from `/path123`.
Input: ({'svcA': {'read_from': '/path123', 'write_to': '/path456'}, 'svcB': {'read_from': '/path456', 'write_to': '/path789'}}, ['/path456'])
Expected Output: ['svcA', 'svcB']
Explanation: `svcA` writes to `/path456` and `svcB` reads from it.
Input: ({'svcA': {'read_from': '/path1', 'write_to': '/path2'}, 'svcB': {'read_from': '/path3', 'write_to': '/path4'}}, ['/missing'])
Expected Output: []
Explanation: No service uses `/missing` exactly.
Input: ({'svcA': {'read_from': '/path1', 'write_to': '/path2'}}, [])
Expected Output: []
Explanation: With no deleted paths, nothing is impacted.
Hints
- Checking membership in a set is much faster than checking membership in a list repeatedly.
- The result order should be deterministic, so sort the impacted service names before returning.
Part 3: Impacted Services Using Directory-Tree Subtree Rules
Constraints
- 0 <= number of services <= 10^4
- 0 <= len(deleted_list) <= 10^4
- Paths are absolute UNIX-like strings beginning with `/`
- Ignore trailing slashes except for the root path `/`
- Each deleted path is either `/` or has at least one non-root parent directory
Examples
Input: ({'svcA': {'read_from': '/data/team1/input.csv', 'write_to': '/tmp/a'}, 'svcB': {'read_from': '/data/team2/input.csv', 'write_to': '/tmp/b'}, 'svcC': {'read_from': '/x', 'write_to': '/data/team1/archive/out.txt'}}, ['/data/team1/deleted.txt'])
Expected Output: ['svcA', 'svcC']
Explanation: The deleted path has parent `/data/team1`, so everything in that subtree is impacted.
Input: ({'svcA': {'read_from': '/logs/app1/in.log', 'write_to': '/out1'}, 'svcB': {'read_from': '/logs/app2/in.log', 'write_to': '/out2'}, 'svcC': {'read_from': '/misc', 'write_to': '/logs/app1/sub/out.log'}}, ['/logs/app1/error.log'])
Expected Output: ['svcA', 'svcC']
Explanation: Only the `/logs/app1` subtree is affected, not sibling subtree `/logs/app2`.
Input: ({'svcA': {'read_from': '/a/b/file1', 'write_to': '/x'}, 'svcB': {'read_from': '/a/b/c/file2', 'write_to': '/y'}, 'svcC': {'read_from': '/a/d/file3', 'write_to': '/z'}}, ['/a/b/lost.txt', '/a/b/c/more.txt'])
Expected Output: ['svcA', 'svcB']
Explanation: The roots `/a/b` and `/a/b/c` reduce to the minimal root `/a/b`.
Input: ({'svcA': {'read_from': '/a/x', 'write_to': '/a/y'}, 'svcB': {'read_from': '/b/x', 'write_to': '/b/y'}}, ['/'])
Expected Output: ['svcA', 'svcB']
Explanation: A deletion at `/` affects the whole tree.
Hints
- Write small helper functions for path normalization, finding a parent directory, and checking whether one path lies inside another subtree.
- If one affected root is already under another affected root, the deeper one is redundant.
Part 4: Propagate Service Impact Through Dependencies in Topological Order
Constraints
- 1 <= number of services <= 2 * 10^4
- 0 <= len(deleted_list) <= 10^4
- Each service has exactly one `read_from` and one `write_to` path
- Service names are unique
- The dependency graph induced by `write_to -> read_from` is a DAG
- Deleted paths in `deleted_list` are distinct
Examples
Input: ({'svcA': {'read_from': '/raw', 'write_to': '/mid'}, 'svcB': {'read_from': '/mid', 'write_to': '/out'}, 'svcC': {'read_from': '/out', 'write_to': '/done'}}, ['/mid'])
Expected Output: {'/mid': ['svcA', 'svcB', 'svcC']}
Explanation: `svcA` and `svcB` are directly impacted by `/mid`, and `svcC` is downstream of `svcB`.
Input: ({'svcA': {'read_from': '/src', 'write_to': '/p'}, 'svcB': {'read_from': '/p', 'write_to': '/q'}, 'svcC': {'read_from': '/p', 'write_to': '/r'}, 'svcD': {'read_from': '/q', 'write_to': '/s'}, 'svcE': {'read_from': '/r', 'write_to': '/t'}}, ['/p'])
Expected Output: {'/p': ['svcA', 'svcB', 'svcC', 'svcD', 'svcE']}
Explanation: The impacted subgraph is a DAG with branching. Lexicographic tie-breaking gives a deterministic topological order.
Input: ({'svcX': {'read_from': '/foo', 'write_to': '/bar'}, 'svcY': {'read_from': '/other', 'write_to': '/baz'}}, ['/foo', '/missing'])
Expected Output: {'/foo': ['svcX'], '/missing': []}
Explanation: `/foo` directly impacts only `svcX`, while `/missing` impacts no service.
Input: ({'svcA': {'read_from': '/a', 'write_to': '/b'}}, [])
Expected Output: {}
Explanation: If there are no deleted paths, the result is an empty dictionary.
Hints
- First build the service dependency graph from matching `write_to` and `read_from` paths.
- For each deleted path, find directly impacted services, do a graph traversal to collect all reachable downstream services, then topologically sort only that impacted subgraph.