Node Reachability: Directed Paths, Undirected Connectivity, and a Removed Direct Edge
Company: Figma
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: hard
Interview Round: Technical Screen
A graph has `n` nodes numbered `0` to `n - 1` and a list of edges. The interview built the problem up in three steps:
1. With the edges read as directed, can `target` be reached from `source`?
2. With the same edges read as undirected, are `source` and `target` connected?
3. If the direct connection between `source` and `target` breaks, is there still another connection between them?
This console version answers all three with one function: `directed` chooses how the edges are read, and `exclude_direct` applies the third step on top of either reading.
### Function Signature
```python
def has_connection(n: int, edges: list[list[int]], source: int, target: int, directed: bool, exclude_direct: bool) -> bool:
```
### Rules
- Each edge `[u, v]` joins nodes `u` and `v`. When `directed` is `True`, it can be followed only from `u` to `v`; when `directed` is `False`, it can be followed in both directions.
- Return `True` if some path leads from `source` to `target` using only usable edges, each followed in an allowed direction, and `False` otherwise.
- When `exclude_direct` is `True`, an edge that joins `source` and `target` directly (`[source, target]` or `[target, source]`) is unusable, and every other edge stays usable. If no such edge exists, the flag changes nothing. When `exclude_direct` is `False`, every edge is usable.
- Do not modify `edges`: the caller keeps using the same list after the call.
### Constraints
- `2 <= n <= 10^5`
- `0 <= len(edges) <= 2 * 10^5`
- Every edge `[u, v]` has `0 <= u, v < n` and `u != v`.
- No edge appears twice, and no pair of nodes is joined in both orders: if `[u, v]` is present, neither another `[u, v]` nor `[v, u]` is.
- `0 <= source, target < n` and `source != target`.
- All values fit in a 32-bit signed integer.
### Examples
**Example 1**
```text
Input: n = 4, edges = [[0, 1], [1, 2], [3, 2]], source = 0, target = 3, directed = True, exclude_direct = False
Output: False
```
Following the edges' direction, node 0 reaches node 1 and then node 2, but the only edge at node 3 points from 3 to 2, so node 3 cannot be reached.
**Example 2**
```text
Input: n = 4, edges = [[0, 1], [1, 2], [3, 2]], source = 0, target = 3, directed = False, exclude_direct = False
Output: True
```
Read as undirected, the edges give the path 0 - 1 - 2 - 3.
**Example 3**
```text
Input: n = 5, edges = [[0, 1], [1, 2], [2, 0], [2, 3], [3, 4]], source = 2, target = 3, directed = False, exclude_direct = True
Output: False
```
Without the edge `[2, 3]`, nodes 0, 1 and 2 are connected to each other and nodes 3 and 4 to each other, but nothing joins the two groups. With the same graph and flags, `source = 0` and `target = 1` would return `True`: ignoring `[0, 1]` still leaves the path 0 - 2 - 1.
Overview: Given a graph of numbered nodes and an edge list, decide whether a target node can be reached from a source node: first along directed edges, then with the edges treated as undirected, and finally when the direct edge between the two nodes may not be used. It tests graph traversal, efficient reachability checks and leaving the caller's input unchanged.
Read the full Figma Software Engineer interview experience this question came from
A graph has `n` nodes numbered `0` to `n - 1` and a list `edges`, where each edge `[u, v]` joins nodes `u` and `v`. You are given two distinct nodes, `source` and `target`, and two boolean flags that decide which edges may be used and in which direction.
The interview built the problem up in three steps: (1) with the edges read as directed, can `target` be reached from `source`? (2) with the same edges read as undirected, are `source` and `target` connected? (3) if the direct connection between `source` and `target` breaks, is there still another connection between them? This function answers all three: `directed` chooses how the edges are read, and `exclude_direct` applies the third step on top of either reading.
**Rules**
- When `directed` is `True`, an edge `[u, v]` can be followed only from `u` to `v`. When `directed` is `False`, it can be followed in both directions.
- When `exclude_direct` is `True`, an edge that joins `source` and `target` directly (stored as `[source, target]` or as `[target, source]`) is unusable, and every other edge stays usable. If no such edge exists, the flag changes nothing. When `exclude_direct` is `False`, every edge is usable.
- Return `True` if some path leads from `source` to `target` using only usable edges, each followed in an allowed direction, and `False` otherwise.
- Do not modify `edges`: the caller keeps using the same list after the call.
**Function signature**
```python
def has_connection(n: int, edges: list[list[int]], source: int, target: int, directed: bool, exclude_direct: bool) -> bool:
```
**Constraints**
- `2 <= n <= 10^5`
- `0 <= len(edges) <= 2 * 10^5`
- Every edge `[u, v]` has `0 <= u, v < n` and `u != v`.
- No edge appears twice, and no pair of nodes is joined in both orders: if `[u, v]` is present, neither another `[u, v]` nor `[v, u]` is.
- `0 <= source, target < n` and `source != target`.
- All values fit in a 32-bit signed integer; no value can exceed 2^31 - 1, so a 32-bit `int` is enough in every language.
**Example 1**
```text
Input: n = 4, edges = [[0, 1], [1, 2], [3, 2]], source = 0, target = 3, directed = True, exclude_direct = False
Output: False
```
Following the edges' direction, node 0 reaches node 1 and then node 2, but the only edge at node 3 points from 3 to 2, so node 3 cannot be reached. With `directed = False` the same input returns `True`: read as undirected, the edges give the path 0 - 1 - 2 - 3.
**Example 2**
```text
Input: n = 5, edges = [[0, 1], [1, 2], [2, 0], [2, 3], [3, 4]], source = 2, target = 3, directed = False, exclude_direct = True
Output: False
```
Without the edge `[2, 3]`, nodes 0, 1 and 2 are connected to each other and nodes 3 and 4 to each other, but nothing joins the two groups. With the same graph and flags, `source = 0` and `target = 1` would return `True`: ignoring `[0, 1]` still leaves the path 0 - 2 - 1.
Constraints
- 2 <= n <= 10^5
- 0 <= len(edges) <= 2 * 10^5
- Every edge [u, v] has 0 <= u, v < n and u != v.
- No edge appears twice, and no pair of nodes is joined in both orders: if [u, v] is present, neither another [u, v] nor [v, u] is.
- 0 <= source, target < n and source != target.
- All values fit in a 32-bit signed integer (no value can exceed 2^31 - 1).
Examples
Input: (2, [], 0, 1, False, False)
Expected Output: False
Explanation: Minimum graph with no edges: source and target are both isolated.
Input: (2, [[0, 1]], 0, 1, True, False)
Expected Output: True
Explanation: Single edge read as directed: 0 -> 1 reaches the target.
Hints
- First pin down which moves each edge [u, v] allows: only from u to v when `directed` is True, both ways when it is False.
- `exclude_direct` sets aside at most one edge: the one whose endpoints are exactly `source` and `target`, whichever order it is stored in. Edges that touch only one of them stay usable.
- A path may pass through up to 10^5 nodes, so make sure each node is handled at most once and that a very long path cannot exhaust a call stack.