tutorial gremlin debugging cosmos-db debugger

How to Debug Gremlin Queries Step by Step

Learn how to debug Gremlin queries using a step-by-step debugger. Stop guessing why your traversal returns no results — inspect intermediate output at every step.

GremlinStudio Team ·

The Frustration of Debugging Gremlin

If you have spent any time writing Gremlin queries against Azure Cosmos DB, you know the feeling. You craft a traversal that looks correct, hit execute, and get back… nothing. No error message, no partial results, just an empty response. The query ran successfully — it simply matched zero traversers somewhere along the way, and you have no idea where.

So you start the ritual. You strip the query down to g.V(), confirm vertices exist, then add steps back one at a time. You sprinkle .count() and .limit(5) throughout the traversal. You copy the query into a text editor and comment out sections. It works, but it is tedious, error-prone, and slow — especially when your traversal is ten or fifteen steps long.

There has to be a better way. And there is.

Why Gremlin Is Uniquely Hard to Debug

Most query languages give you some form of execution plan or step-by-step feedback. SQL has EXPLAIN. MongoDB has .explain(). But Gremlin traversals are fundamentally different, and that makes them harder to inspect.

Composable steps hide complexity. A Gremlin traversal is a chain of steps, each transforming the stream of traversers from the previous step. The final result only reflects the last step — everything in between is invisible unless you explicitly check it.

Lazy evaluation obscures behavior. Gremlin steps are lazily evaluated, meaning they do not execute until a terminal step pulls data through the pipeline. This makes it difficult to reason about what happens at each stage.

Cosmos DB adds its own quirks. The Gremlin API on Azure Cosmos DB does not support all TinkerPop steps, partition-key routing affects which vertices are reachable, and errors often manifest as empty results rather than exceptions. The .executionProfile() step can show RU costs, but it does not break down what each step produced.

“No results” is the most common error. Unlike a SQL syntax error that points you to a specific line, a Gremlin traversal that filters too aggressively just returns an empty set. The question is always: which step eliminated all the traversers?

The Traditional Debugging Approach

Most developers debug Gremlin queries by manually decomposing them. Here is what that typically looks like.

Say you have this query that should return the names of Alice’s friends over 25:

g.V().has('person','name','Alice').out('knows').has('age',gt(25)).values('name')

It returns nothing. So you start peeling it apart:

g.V().has('person','name','Alice')

Does Alice exist? You run it — one vertex comes back. Good.

g.V().has('person','name','Alice').out('knows')

Does she have outgoing knows edges? You run it — three vertices. Still good.

g.V().has('person','name','Alice').out('knows').has('age',gt(25))

Here it drops to zero. Now you know the filter is the problem. Maybe the age property is stored as a string instead of a number, or maybe all her friends are 25 or younger.

This manual process works, but it requires you to re-execute the query multiple times, each time copying and editing the text. For a five-step query that is manageable. For a query with nested coalesce(), choose(), or union() steps, it becomes a real time sink.

For Cosmos DB specifically, you might also use .executionProfile() to inspect RU consumption:

g.V().has('person','name','Alice').out('knows').has('age',gt(25)).executionProfile()

This tells you the cost of the query, but it still does not show you the intermediate result set at each step.

The Step-by-Step Debugger in GremlinStudio

GremlinStudio includes a built-in Gremlin debugger that automates exactly the process described above — but without the manual query editing. You write your full traversal, click Debug, and the debugger executes each step independently, showing you the intermediate results and traverser count at every stage.

Here is how it works with our example query:

g.V().has('person','name','Alice').out('knows').has('age',gt(25)).values('name')

Step 1: g.V() — The debugger executes just this step and shows all vertices in the graph. The result panel displays the full vertex list, and the graph visualization highlights every node. You can see you are starting from the entire graph.

Step 2: .has('person','name','Alice') — Now the debugger adds the filter. The result narrows to a single vertex. On the graph canvas, only Alice’s node is highlighted — everything else dims. Traverser count: 1.

Step 3: .out('knows') — The debugger traverses Alice’s outgoing knows edges. Three vertices appear in the results: Bob, Carol, and Dave. The graph highlights these three nodes and the edges connecting them to Alice. Traverser count: 3.

Step 4: .has('age',gt(25)) — This is the critical step. The debugger applies the age filter, and you immediately see the traverser count drop — maybe to zero, maybe to one. If it drops to zero, you know every friend has age less than or equal to 25 (or the property is missing or stored as a string). You can inspect the properties of Bob, Carol, and Dave in the previous step to understand why.

Step 5: .values('name') — The final step extracts name strings from whatever vertices survived. If Step 4 returned results, you see the names here.

The key insight is that you never edited the query. You wrote it once, clicked Debug, and stepped through it. The debugger parsed the traversal into individual steps and ran progressively longer prefixes of your query, showing you exactly where results appear, change, or disappear.

When to Use the Debugger

The debugger is most valuable in specific scenarios where the traditional approach is painful:

Complex filters returning no results. When a chain of .has(), .where(), and .not() steps produces an empty set, the debugger pinpoints exactly which filter eliminated the last traverser.

Joins behaving unexpectedly. If .out() or .both() returns fewer results than you expected, stepping through the traversal shows whether the issue is missing edges, wrong edge labels, or partition boundaries in Cosmos DB.

Aggregation confusion. Steps like .fold(), .unfold(), .group(), and .aggregate() transform the traverser stream in non-obvious ways. The debugger shows you the shape of data before and after each aggregation step, making it clear whether a .fold() collapsed everything into a single list or whether .unfold() expanded it back correctly.

Dedup removing too many results. When .dedup() is filtering more aggressively than expected, you can compare the traverser count before and after to understand the scope of duplication.

Tips for Effective Debugging

Start with the full query. Do not simplify first. Write the query you actually want, then use the debugger to find where it breaks. This saves you from accidentally introducing or masking bugs during manual simplification.

Watch the traverser count. The most useful signal is the count at each step. A sudden drop from many traversers to zero tells you exactly where to focus your investigation.

Use the graph visualization. The debugger highlights active traversers on the graph canvas. For traversal queries (out(), in(), both()), seeing which nodes light up at each step gives you spatial intuition about how the query navigates your graph.

Compare adjacent steps. When results change unexpectedly between steps, inspect the properties of the items in the previous step. Often the issue is a typo in a property name, a type mismatch (string vs. number), or a missing property on some vertices.

Debug expensive queries before running them. If you suspect a query might fan out across many partitions, stepping through it lets you see the scope of each step before the full traversal hits your Cosmos DB account. You can catch accidental full-graph scans early.

Start Debugging Smarter

The step-by-step Gremlin debugger is available in GremlinStudio’s Pro tier. Download the free 7-day trial to try it on your own graph data — no credit card required. For a full walkthrough of all debugger features, see the debugger documentation.

Stop guessing why your Gremlin queries return empty results. Step through them and see exactly what happens at every stage of the traversal.