This page covers all control flow statements available in stored procedure bodies.
Conditional branching:
Procedure Body LanguageIF age > 18 { PRINT 'Adult' } ELSE IF age > 12 { PRINT 'Teenager' } ELSE { PRINT 'Child' }
Conditions can use any expression that evaluates to a boolean:
Procedure Body LanguageIF n.active = true AND OUT_DEGREE(n) > 5 { PRINT 'Active high-degree node' } IF score IS NOT NULL { RETURN score } IF SIZE(friends) > 0 { -- process friends }
Procedure Body LanguageFOR item IN [1, 2, 3, 4, 5] { PRINT item }
Procedure Body LanguageFOR i IN RANGE(0, 10) { PRINT i -- prints 0 to 9 } FOR i IN RANGE(1, 100) { -- process items 1 to 99 }
Procedure Body LanguageFOR n IN SCAN(:Person) { PRINT n.name }
Procedure Body LanguageFOR e IN EDGES(:KNOWS) { PRINT e._from || ' -> ' || e._to }
Procedure Body LanguageFOR neighbor IN NEIGHBORS(n, OUT, :KNOWS) { PRINT neighbor.name }
See Iterators and Traversal for full details on iterator sources.
Executes loop iterations across multiple worker goroutines:
Procedure Body Language-- Auto-detect worker count PARALLEL FOR n IN SCAN(:Person) { n.processed = true } -- Explicit worker count PARALLEL FOR n IN SCAN(:Person) WORKERS 8 { LET score = OUT_DEGREE(n) * 0.1 n.score = score }
See Parallel Execution for complete parallel execution guide.
Procedure Body LanguageLET i = 0 WHILE i < 10 { PRINT i LET i = i + 1 }
Iterates until convergence:
Procedure Body LanguageLET changed = 1 LET iteration = 0 WHILE changed > 0 { LET changed = 0 PARALLEL FOR n IN SCAN() WORKERS 8 { LET current = GET_SLICE_PROP(n._internal_id, 'rank') LET new_val = compute_new_rank(n) IF new_val <> current { SET_SLICE_PROP(n._internal_id, 'rank', new_val) LET changed = changed + 1 } } LET iteration = iteration + 1 PRINT 'Iteration ' || TOSTRING(iteration) || ': ' || TOSTRING(changed) || ' changed' }
Exit the innermost loop immediately:
Procedure Body LanguageFOR i IN RANGE(1, 1000) { IF i > 50 { BREAK } PRINT i }
Works with FOR, PARALLEL FOR, WHILE, and FOR...IN MATCH:
Procedure Body LanguageFOR (n, depth) IN MATCH BFS (start)-[:KNOWS]->{1,10}(n) { IF depth > 5 { BREAK -- stop traversal early } RETURN n._id, depth }
Skip the rest of the current iteration and move to the next:
Procedure Body LanguageFOR i IN RANGE(1, 100) { IF i % 2 = 0 { CONTINUE -- skip even numbers } PRINT i -- only prints odd numbers }
Procedure Body LanguageFOR n IN SCAN(:Person) { IF n.active = false { CONTINUE -- skip inactive nodes } -- process only active nodes RETURN n._id AS person_id }
Catches runtime errors and prevents them from terminating the procedure. If any statement in the TRY block fails, execution jumps to the CATCH block. If no error occurs, the CATCH block is skipped.
Procedure Body LanguageTRY { LET result = risky_operation() PRINT result } CATCH { PRINT 'Operation failed' }
Use CATCH (e) to capture the error. The error variable has two properties:
| Property | Description |
|---|---|
e.message | Error message string |
e.code | Error code (e.g., EXECUTION_ERROR, USER_ERROR) |
Procedure Body LanguageTRY { LET val = 1 / 0 } CATCH (e) { PRINT 'Error: ' || e.message PRINT 'Code: ' || e.code }
When an error occurs in the TRY block:
TRY statements are skippedCATCH block executesTRY/CATCH blockProcedure Body LanguageTRY { PRINT 'Step 1' -- runs LET x = bad_call() -- error here PRINT 'Step 2' -- skipped } CATCH (e) { PRINT 'Caught: ' || e.message -- runs } PRINT 'Continues' -- runs
Use bare THROW inside a CATCH block to re-throw the caught error. This is useful for logging or cleanup before propagating the error:
Procedure Body LanguageTRY { dangerous_action() } CATCH (e) { PRINT 'Logging error: ' || e.message THROW -- re-throw the original error }
TRY/CATCH blocks can be nested. Each block handles its own errors independently:
Procedure Body LanguageTRY { TRY { LET x = risky_step_1() } CATCH (e1) { PRINT 'Step 1 failed: ' || e1.message } -- continues even if step 1 failed LET y = risky_step_2() } CATCH (e2) { PRINT 'Step 2 failed: ' || e2.message }
Raise an error explicitly:
Procedure Body LanguageIF $iterations < 1 { THROW 'Iterations must be at least 1' } IF n IS NULL { THROW 'Node not found: ' || $node_id }
Re-throw inside a CATCH block (no argument):
Procedure Body LanguageTRY { process() } CATCH (e) { -- cleanup... THROW -- re-throw original error }
All statements inside execute as a single transaction. If any statement fails, all changes are rolled back:
Procedure Body LanguageATOMIC { INSERT (:Account {_id: 'a1', balance: 100}) INSERT (:Account {_id: 'a2', balance: 200}) INSERT (a1)-[:TRANSFER {amount: 50}]->(a2) }
Use cases:
Procedure Body LanguageATOMIC { MATCH (src:Account {_id: $from_id}) MATCH (dest:Account {_id: $to_id}) SET src.balance = src.balance - $amount SET dest.balance = dest.balance + $amount INSERT (src)-[:TRANSFER {amount: $amount, timestamp: TIMESTAMP_MS()}]->(dest) }
Control flow statements can be nested arbitrarily:
Procedure Body LanguageFOR n IN SCAN(:Person) { IF OUT_DEGREE(n) > 10 { FOR neighbor IN NEIGHBORS(n, OUT, :KNOWS) { IF neighbor.active = true { TRY { LET score = JACCARD_SIMILARITY(n, neighbor) IF score > 0.5 { RETURN n._id AS source, neighbor._id AS target, score } } CATCH { CONTINUE } } } } }