Although Cypher uses the term "path", these actually adhere to the "trail" technical definition: nodes can be revisited, but no relationship can be repeated per Cypher "path". This is also referred to as relationship isomorphism.
More info on that here:
shortestPath(), when using
shortestPath() in a MATCH, this should return actual "paths" that also adhere to the technical definition. By default no repeat of a node or relationship can happen per path, because a repeat would mean that there is a shorter path not taken. shortestPath() uses a breadth-first exploration, so a shortest path will be found naturally before any longer path with repeats can be found. The caveat here is that when using
shortestPath(), the start and end nodes MUST be known and found first before the expansion. This is the difference between "find me the shortest path from this starting node to the first node of this other type" (which it does NOT do, and if you need to do this then you need to use a different approach), vs "for every combination of this starting node and every node of this other type, find me the shortest path for each of those resulting pairs" (which is how shortestPath() behaves).
When using the traversal API or APOC path expander procedures, you have additional options for how to handle uniqueness during expansion. Here are the types that can be used:
The notable ones here are RELATIONSHIP_PATH (this is identical to Cypher's default relationship isomorphism during expansion), NODE_PATH (nodes must be unique per path, so this fits the technical definition of a "trail"), and NODE_GLOBAL (a node can only be visited once across all paths generated from the expansion...this means that once a path is found to a given node, no other alternate path to that node will be considered. This is good for reachability queries).
GDS has its own pathfinding algos that can be useful depending on what you need: