Discussion: Breadth-first execution as an alternative to depth-first traversal
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 28/100
- Issue type
- Feature
- Clarity
- Needs clarification
- Activity status
- Quiet
- Tech stack
- graphql, typescript
- Domain
- api, backend-api-design
Research direction
Start by reading the execution path named in the discussion: executeFields, executeField, completeValue, completeObjectValue, and completeListValue. Compare that behavior with graphql-spec#1039, graphql-spec#985, Cardinal, and Grafast. Done would require a maintainer decision on whether breadth-first execution belongs in graphql-js and a defined implementation scope.
Written by the indexing model from the issue text.
Description
Shopify recently published Shopify's journey to faster breadth-first GraphQL execution, which presents detailed benchmarks comparing depth-first and breadth-first execution strategies for high-cardinality list queries. The article specifically references graphql-js as the spec implementation that established the depth-first pattern.
Their findings are significant: for large list queries (e.g. 250 products × 250 variants), breadth-first execution achieved up to 15× faster execution with 90% less memory in their production Ruby stack. The core insight is that depth-first traversal multiplies per-field overhead (resolver setup, instrumentation, promise allocation) by the number of list items, while breadth-first execution resolves each field once across all objects at a given depth level.
The depth-first pattern in graphql-js
The current execution model in executeFields → executeField → completeValue → completeObjectValue → executeFields is recursive per-object. When completeListValue processes a list, each item independently descends its full subtree before the next item is touched. There is no cross-item field batching at any level — each object gets its own recursive resolution chain.
For a query like:
{
products(first: 1000) {
title
variants(first: 100) {
price
}
}
}
This results in 1,000 calls to the title resolver, 1,000 calls to the variants resolver, and 100,000 calls to the price resolver — each with its own field-level overhead. A breadth-first engine would make 1 + 1 + 1 = 3 resolver invocations (each receiving the full set of objects at that level).
Existing work
This builds on prior discussion in #4024 and the broader ecosystem:
- GraphQL Cardinal — Shopify's breadth-first execution engine (Ruby)
- Grafast — A planning-based TypeScript executor using breadth-first resolution
- The spec itself permits alternative execution algorithms as long as the perceived result is equivalent
Questions for the community
- Has there been consideration of exploring breadth-first execution within
graphql-jsitself, given its role as the reference implementation? - Would a breadth-first execution mode (opt-in or otherwise) be in scope for this project, or is this better suited to alternative executors like Grafast?
- The spec changes in graphql/graphql-spec#1039 (collected fields) and graphql/graphql-spec#985 (relaxed parallel execution) seem to lay groundwork for this. Are there spec-level concerns that would block a breadth-first approach in the reference implementation?
The napkin math from the article and its production results are compelling. Curious to hear the maintainers' perspective on whether this execution model has a place in graphql-js, or if the reference implementation intentionally mirrors the spec's depth-first algorithmic description.
- Dominant language
- TypeScript
- Stars
- 20.3k
- Forks
- 2.1k
- Avg merge
- 6h 51m
- Merged PRs (30d)
- 117
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from graphql/graphql-js
-
Difficulty 2/5 1-3 hours Newbie friendliness 67/100
graphql/graphql-js#5001 ·
Maintainers usually reply within 1 day
-
print() can change a document's meaning: shorthand query after a definition without a bodyPossibly taken @nimbly1 claimed this 20 days ago. Open
Difficulty 3/5 Half a day Newbie friendliness 82/100
graphql/graphql-js#4901 · 2 reactions ·
Maintainers usually reply within 1 day
-
api-testOpen
Difficulty 5/5 Over a week Newbie friendliness 10/100
graphql/graphql-js#4848 ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
graphql/graphql-js#4834 · 5 comments ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 35/100
graphql/graphql-js#4822 ·
Maintainers usually reply within 1 day
All issues in graphql/graphql-js
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
Add "append" and "prepend" slots for vue-vuetify extended renderersPossibly taken @simeyc claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
eclipsesource/jsonforms#2668 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
cobot/zapier#520 · 2 comments ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
README の通知条件を実際の判定処理に合わせるPossibly taken @unhappychoice claimed this today. Openfactory:in-progress priority:medium
Difficulty 1/5 Under an hour Newbie friendliness 90/100
unhappychoice/oss-issue-opener#395 · 1 comment ·
Maintainers usually reply within 1 day