Skip to content

Commit 300493e

Browse files
authored
fix(core): use Set for O(1) visited node lookup in hasPath (#33754)
## Current Behavior The `hasPath` function in `graph.ts` uses an array with `indexOf()` for tracking visited nodes during recursive graph traversal: ```typescript function hasPath(graph, target, node, visited: string[]) { for (let d of graph.dependencies[node] || []) { if (visited.indexOf(d.target) > -1) continue; // O(n) lookup visited.push(d.target); // recursive call... } } ``` This results in O(n) lookups per node visited, making worst-case traversal O(n²). ## Expected Behavior Use `Set` for O(1) visited node tracking: ```typescript function hasPath(graph, target, node, visited: Set<string>) { for (const d of graph.dependencies[node] || []) { if (visited.has(d.target)) continue; // O(1) lookup visited.add(d.target); // recursive call... } } ``` ## Performance Impact ``` Before (Array + indexOf): After (Set + has): ┌─────────────────────────┐ ┌─────────────────────────┐ │ hasPath() called │ │ hasPath() called │ └───────────┬─────────────┘ └───────────┬─────────────┘ │ │ ▼ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ │ visited.indexOf(target) │ │ visited.has(target) │ │ O(n) lookup │ │ O(1) lookup │ └───────────┬─────────────┘ └───────────┬─────────────┘ │ │ ▼ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ │ visited.push(target) │ │ visited.add(target) │ │ O(1) │ │ O(1) │ └───────────┬─────────────┘ └───────────┬─────────────┘ │ │ ▼ ▼ Complexity: O(n²) Complexity: O(n) for full traversal for full traversal ``` **Example with 500 nodes:** - Before: 500 nodes × avg 250 indexOf lookups = ~125,000 comparisons - After: 500 nodes × 1 Set lookup each = 500 operations ## Why Accept This PR 1. **Zero risk**: Same semantics, just faster data structure 2. **Standard pattern**: Set is the idiomatic choice for visited tracking in graph algorithms 3. **Measurable impact**: Graph filtering with `--focus` flag will be significantly faster on large monorepos ## Related Issue(s) Contributes to #32265 ## Merge Dependencies This PR has no dependencies and can be merged independently. ---
1 parent c6b2a26 commit 300493e

1 file changed

Lines changed: 6 additions & 5 deletions

File tree

  • packages/nx/src/command-line/graph

packages/nx/src/command-line/graph/graph.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,13 @@ function hasPath(
183183
graph: ProjectGraph,
184184
target: string,
185185
node: string,
186-
visited: string[]
186+
visited: Set<string>
187187
) {
188188
if (target === node) return true;
189189

190-
for (let d of graph.dependencies[node] || []) {
191-
if (visited.indexOf(d.target) > -1) continue;
192-
visited.push(d.target);
190+
for (const d of graph.dependencies[node] || []) {
191+
if (visited.has(d.target)) continue;
192+
visited.add(d.target);
193193
if (hasPath(graph, target, d.target, visited)) return true;
194194
}
195195
return false;
@@ -210,7 +210,8 @@ function filterGraph(
210210
filteredProjectNames = new Set<string>();
211211
projectNames.forEach((p) => {
212212
const isInPath =
213-
hasPath(graph, p, focus, []) || hasPath(graph, focus, p, []);
213+
hasPath(graph, p, focus, new Set()) ||
214+
hasPath(graph, focus, p, new Set());
214215

215216
if (isInPath) {
216217
filteredProjectNames.add(p);

0 commit comments

Comments
 (0)