Tuesday, 12 May 2026

Bidirectional Traversals (both()): Exploring All Adjacent Vertices

  

So far, two fundamental traversal patterns have been established.

 

·      out(): follow outgoing edges

·      in(): follow incoming edges

 

These steps are ideal when the direction of the relationship is semantically important, such as “customer placed order” or “order delivered to city”.

 

However, not all graph questions care about direction. Sometimes the question is simply:

 

·      “What is connected to this vertex?”

·      “What are all the immediate neighbors?”

·      “Show me everything one hop away, regardless of direction.”

 

This is exactly where the both() step is used.

 

1. Incoming and Outgoing Adjacency

A vertex U is considered adjacent to vertex V if there exists any edge between them—regardless of whether the edge points from U to V or from V to U.

 

In other words:

·      U --(edge)--> V

·      V --(edge)--> U

 

Both cases establish adjacency. The both() step traverses all such adjacent vertices.

 

The both() step performs the following operation:

 

·      Start at the current vertex

·      Traverse all incoming and outgoing edges

·      Emit the adjacent vertices at the other end of those edges

 

Formally, both() is equivalent to out() + in() combined into a single step.

 

Edge Label Filtering

Just like out() and in(), both() can be restricted by edge labels:

 

·      both(): follow all edges (incoming and outgoing)

·      both('contains'): follow only contains edges, in both directions

·      both('placed', 'contains'): follow multiple edge labels

 

This allows controlled bidirectional traversal without losing semantic intent.

 

2. Example: An E-Commerce Graph

To make the discussion concrete, consider an e-commerce domain modeled as a property graph. The graph contains:

 

Vertex Types

·      city: Bengaluru, Hyderabad, Chennai, Pune

·      category: Electronics, Books, Clothing

·      product: Laptop, Smartphone, Java Programming Book, T-Shirt

·      customer: Amit Sharma, Priya Iyer, Rohit Verma

·      order: O9001 to O9005

 

Edge Types

·      belongsTo: product category

·      placed: customer order

·      contains: order product

·      deliveredTo: order city

 

Each edge has a clear semantic direction that aligns with real-world meaning.

 

Gremlin Statements to build the Graph

graph = TinkerGraph.open()
g = graph.traversal()

blr = g.addV('city').property('name', 'Bengaluru').next()
hyd = g.addV('city').property('name', 'Hyderabad').next()
chn = g.addV('city').property('name', 'Chennai').next()
pne = g.addV('city').property('name', 'Pune').next()

catElectronics = g.addV('category').property('name', 'Electronics').next()
catBooks       = g.addV('category').property('name', 'Books').next()
catClothing    = g.addV('category').property('name', 'Clothing').next()

p1 = g.addV('product').property('sku', 'P1001').property('name', 'Laptop').next()
p2 = g.addV('product').property('sku', 'P1002').property('name', 'Smartphone').next()
p3 = g.addV('product').property('sku', 'P2001').property('name', 'Java Programming Book').next()
p4 = g.addV('product').property('sku', 'P3001').property('name', 'T-Shirt').next()

g.V(p1).addE('belongsTo').to(catElectronics).next()
g.V(p2).addE('belongsTo').to(catElectronics).next()
g.V(p3).addE('belongsTo').to(catBooks).next()
g.V(p4).addE('belongsTo').to(catClothing).next()


c1 = g.addV('customer').property('customerId', 'C101').property('name', 'Amit Sharma').next()
c2 = g.addV('customer').property('customerId', 'C102').property('name', 'Priya Iyer').next()
c3 = g.addV('customer').property('customerId', 'C103').property('name', 'Rohit Verma').next()

o1 = g.addV('order').property('orderId', 'O9001').next()
o2 = g.addV('order').property('orderId', 'O9002').next()
o3 = g.addV('order').property('orderId', 'O9003').next()
o4 = g.addV('order').property('orderId', 'O9004').next()
o5 = g.addV('order').property('orderId', 'O9005').next()

g.V(c1).addE('placed').to(o1).next()
g.V(c1).addE('placed').to(o2).next()

g.V(c2).addE('placed').to(o3).next()

g.V(c3).addE('placed').to(o4).next()
g.V(c3).addE('placed').to(o5).next()


g.V(o1).addE('contains').to(p1).next()
g.V(o1).addE('contains').to(p3).next()

g.V(o2).addE('contains').to(p2).next()

g.V(o3).addE('contains').to(p4).next()

g.V(o4).addE('contains').to(p1).next()
g.V(o4).addE('contains').to(p4).next()

g.V(o5).addE('contains').to(p3).next()


g.V(o1).addE('deliveredTo').to(blr).next()
g.V(o2).addE('deliveredTo').to(blr).next()

g.V(o3).addE('deliveredTo').to(hyd).next()

g.V(o4).addE('deliveredTo').to(chn).next()

g.V(o5).addE('deliveredTo').to(pne).next()

   

Example 1: Print all vertices and edges

 

gremlin> g.V().valueMap(true)
==>[id:0,label:city,name:[Bengaluru]]
==>[id:33,label:customer,customerId:[C102],name:[Priya Iyer]]
==>[id:2,label:city,name:[Hyderabad]]
==>[id:4,label:city,name:[Chennai]]
==>[id:36,label:customer,customerId:[C103],name:[Rohit Verma]]
==>[id:6,label:city,name:[Pune]]
==>[id:39,label:order,orderId:[O9001]]
==>[id:8,label:category,name:[Electronics]]
==>[id:41,label:order,orderId:[O9002]]
==>[id:10,label:category,name:[Books]]
==>[id:43,label:order,orderId:[O9003]]
==>[id:12,label:category,name:[Clothing]]
==>[id:45,label:order,orderId:[O9004]]
==>[id:14,label:product,name:[Laptop],sku:[P1001]]
==>[id:47,label:order,orderId:[O9005]]
==>[id:17,label:product,name:[Smartphone],sku:[P1002]]
==>[id:20,label:product,name:[Java Programming Book],sku:[P2001]]
==>[id:23,label:product,name:[T-Shirt],sku:[P3001]]
==>[id:30,label:customer,customerId:[C101],name:[Amit Sharma]]
gremlin> 
gremlin> 
gremlin> 
gremlin> g.E().valueMap(true)
==>[id:64,label:deliveredTo]
==>[id:65,label:deliveredTo]
==>[id:49,label:placed]
==>[id:50,label:placed]
==>[id:51,label:placed]
==>[id:52,label:placed]
==>[id:53,label:placed]
==>[id:54,label:contains]
==>[id:55,label:contains]
==>[id:56,label:contains]
==>[id:57,label:contains]
==>[id:26,label:belongsTo]
==>[id:58,label:contains]
==>[id:27,label:belongsTo]
==>[id:59,label:contains]
==>[id:28,label:belongsTo]
==>[id:60,label:contains]
==>[id:29,label:belongsTo]
==>[id:61,label:deliveredTo]
==>[id:62,label:deliveredTo]
==>[id:63,label:deliveredTo]

   

Example 2: One-Hop Neighborhood of an Order

Suppose the goal is to inspect everything directly connected to an order, without caring about direction.

 

g.V().
  has('order', 'orderId', 'O9001').
  both().
  valueMap(true)

gremlin> g.V().
......1>   has('order', 'orderId', 'O9001').
......2>   both().
......3>   valueMap(true)
==>[id:14,label:product,name:[Laptop],sku:[P1001]]
==>[id:20,label:product,name:[Java Programming Book],sku:[P2001]]
==>[id:0,label:city,name:[Bengaluru]]
==>[id:30,label:customer,customerId:[C101],name:[Amit Sharma]]

 

Example 3: All Vertices Related to a Product

A product participates in two relationships.

Incoming: contains (orders)

Outgoing: belongsTo (category)

 

g.V().
  has('product', 'name', 'Laptop').
  both().
  valueMap(true)

gremlin> g.V().
......1>   has('product', 'name', 'Laptop').
......2>   both().
......3>   valueMap(true)
==>[id:8,label:category,name:[Electronics]]
==>[id:39,label:order,orderId:[O9001]]
==>[id:45,label:order,orderId:[O9004]]

   

 

Example 4: Bidirectional Traversal with Edge Label Filtering

To traverse only through contains edges, regardless of direction.

 

g.V().
  has('product', 'name', 'Laptop').
  both('contains').
  valueMap(true)

gremlin> g.V().
......1>   has('product', 'name', 'Laptop').
......2>   both('contains').
......3>   valueMap(true)
==>[id:39,label:order,orderId:[O9001]]
==>[id:45,label:order,orderId:[O9004]]

   

Example 5: Exploring a Customer’s Immediate Graph Context

 

g.V().
  has('customer', 'name', 'Rohit Verma').
  both().
  valueMap(true)

gremlin> g.V().
......1>   has('customer', 'name', 'Rohit Verma').
......2>   both().
......3>   valueMap(true)
==>[id:45,label:order,orderId:[O9004]]
==>[id:47,label:order,orderId:[O9005]]

   

 

The choice between out(), in(), and both() reflects intent:

·      Use out() when the meaning of direction matters

·      Use in() when asking reverse or impact-style questions

·      Use both() when performing structural exploration

 

both() is particularly valuable for:

·      Graph discovery and debugging

·      Visualizations and neighborhood inspection

·      Algorithms that treat edges as undirected

·      Generic tooling where edge direction is unknown or irrelevant

 

both() Compared to out() and in()

Traversal Intent

Recommended Step

Follow domain flow

out()

Ask “who/what led here?”

in()

Explore all neighbors

both()

Build undirected-style queries

both()

 

While both() can always replace in() + out(), it should be used deliberately to avoid unintentionally broad traversals.

 

In summary,

 

both() traverses incoming and outgoing adjacent vertices

 

·      It is direction-agnostic but label-aware

·      Ideal for neighborhood exploration and structural queries

·      Complements out() and in() rather than replacing them

·      Clear graph modeling keeps both() safe and expressive

 

With out(), in(), and both() together, the foundational navigation toolkit of Gremlin is complete and enabling expressive, precise, and intuitive traversal of property graphs.

  

  

Previous                                                    Next                                                    Home

No comments:

Post a Comment