Implement Recursive Algorithms with Sub-workflows: Towers of Hanoi Demo
https://n8nworkflows.xyz/workflows/implement-recursive-algorithms-with-sub-workflows--towers-of-hanoi-demo-8656
# Implement Recursive Algorithms with Sub-workflows: Towers of Hanoi Demo
### 1. Workflow Overview
This workflow demonstrates how to implement the recursive algorithm for the classic Towers of Hanoi puzzle using n8n's sub-workflows. It serves as a proof of concept for recursion handling within n8n by:
- Recursively solving the Towers of Hanoi problem for a given number of discs.
- Using sub-workflows to model recursive calls.
- Maintaining and updating stacks (rods) representing disc positions.
- Logging each move and formatting the final solution as human-readable instructions.
The workflow logically separates into these blocks:
- **1.1 Input Reception and Initialization:** Accepts the number of discs and initializes stacks representing rods A, B, and C.
- **1.2 Recursive Decision (Base Case and Recursive Calls):** Checks if only one disc remains (base case) or recurses on smaller subproblems.
- **1.3 Disc Movement and Stack Updates:** Moves discs between stacks and logs the steps.
- **1.4 Recursive Sub-workflow Invocations:** Calls the main workflow recursively with updated parameters to simulate recursive calls.
- **1.5 Result Formatting:** Converts the logged moves into a formatted textual solution.
---
### 2. Block-by-Block Analysis
#### 2.1 Input Reception and Initialization
- **Overview:**
This block sets the initial number of discs, enforces constraints on the allowed disc count, and initializes the stacks representing rods A, B, and C with their respective discs.
- **Nodes Involved:**
- Start (Manual Trigger)
- Set number of discs
- Create stacks
- **Node Details:**
- **Start**
- Type: Manual Trigger
- Role: Initiates workflow execution manually.
- Input/Output: No input; triggers the workflow start.
- Edge cases: None.
- **Set number of discs**
- Type: Set
- Role: Defines a numeric variable `numberOfDiscs` (default 4).
- Configuration: Sets `numberOfDiscs` to 4 initially.
- Input: Triggered by Start node.
- Output: Passes data to `Create stacks`.
- Edge cases: None.
- **Create stacks**
- Type: Code (JavaScript)
- Role: Validates `numberOfDiscs` (min 1, max 5) and creates three stacks (A, B, C) with discs initialized on stack A in descending order. Also initializes an empty logs array.
- Key logic:
- Limits discs between 1 and 5 to avoid recursion depth issues.
- Prepares `stackA`, `stackB`, `stackC` objects with `name` and `items` arrays.
- `stackA.items` contains discs [numberOfDiscs ... 1].
- `stackB.items` and `stackC.items` are empty arrays.
- Logs array is empty.
- Input: Receives `numberOfDiscs` from previous node.
- Output: Passes stacks and logs to sub-workflow `A B C`.
- Edge cases: Input number outside allowed range adjusted automatically.
---
#### 2.2 Recursive Decision (Base Case and Recursive Calls)
- **Overview:**
Decides whether the current recursion step is the base case (one disc) or requires further recursion. Directs flow accordingly.
- **Nodes Involved:**
- A B C (Execute Workflow - recursive call)
- Max 1 disc (If node)
- X to Z (Code)
- X Z Y (Execute Workflow - recursive call)
- Y X Z (Execute Workflow - recursive call)
- Update (Code)
- Update (Code)
- **Node Details:**
- **A B C**
- Type: Execute Workflow (Sub-workflow call)
- Role: Recursively calls the main workflow with inputs: current stacks (A, B, C), logs, and `numberOfDiscs`.
- Configuration: Waits for sub-workflow to finish to continue processing.
- Input: Receives initialized stacks and logs from `Create stacks`.
- Output: Passes results to `Max 1 disc`.
- Edge cases: Recursive calls can fail due to stack overflow if max recursion depth exceeded.
- **Max 1 disc**
- Type: If
- Role: Checks if `numberOfDiscs` ≤ 1 (base case).
- Configuration: Condition `numberOfDiscs <= 1`.
- Input: Receives current state from `A B C`.
- Output:
- True branch: `X to Z` (move disc directly).
- False branch: `X Z Y` (recursive call with swapped stacks).
- Edge cases: Expression evaluation errors if input is missing or malformed.
- **X to Z**
- Type: Code
- Role: Moves one disc from stackX to stackZ and logs the move.
- Logic:
- Pops disc from `stackX.items`, pushes to `stackZ.items`.
- Appends a string `"stackX.name stackZ.name disc"` to logs.
- Input: Receives stacks and logs from `Max 1 disc` true branch.
- Output: Passes updated stacks/logs to `Y X Z`.
- Edge cases: Popping from empty stack will cause errors; ensure input stacks are valid.
- **X Z Y**
- Type: Execute Workflow (Sub-workflow call)
- Role: Recursive call with `numberOfDiscs - 1` and stacks reordered to (stackX, stackZ, stackY).
- Input: Receives updated stacks and logs from `Max 1 disc` false branch.
- Output: Passes results to `Update` node.
- Edge cases: Recursive calls may cause stack depth issues.
- **Y X Z**
- Type: Execute Workflow (Sub-workflow call)
- Role: Recursive call with `numberOfDiscs - 1` and stacks reordered to (stackY, stackX, stackZ).
- Input: Receives updated stacks and logs from `X to Z`.
- Output: Passes results to `Update` node.
- Edge cases: Same as other recursion calls.
- **Update** (two instances: one after `X Z Y`, one after `Y X Z`)
- Type: Code
- Role: Corrects stack assignments and increments `numberOfDiscs` to restore state after recursive calls.
- Logic differences:
- One swaps stacks Y and Z to reset ordering after `X Z Y`.
- The other swaps stacks X and Y after `Y X Z`.
- Input: Receives results from respective sub-workflow calls.
- Output: Passes updated data back to `X to Z` or `Solution`.
- Edge cases: Errors if input data structure is unexpected.
---
#### 2.3 Disc Movement and Stack Updates
- **Overview:**
Handles the actual movement of discs between stacks and keeps a log of moves.
- **Nodes Involved:**
- X to Z (Code)
- X to Z (Code) [duplicate node name but different instance]
- Update (Code)
- Update (Code)
- **Node Details:**
- **X to Z** (first instance)
- See section 2.2 for details.
- ** X to Z** (second instance)
- Type: Code
- Role: Same as the first `X to Z`, moves disc from stackX to stackZ and logs the move.
- Positioned differently in the flow but functionally equivalent.
- Edge cases: Same as above.
- **Update** and ** Update**
- See section 2.2 for details.
---
#### 2.4 Result Formatting
- **Overview:**
Transforms the collected logs of disc movements into a readable, formatted text output describing each move step-by-step.
- **Nodes Involved:**
- Solution (Code)
- **Node Details:**
- **Solution**
- Type: Code
- Role: Converts the array of logged moves into a formatted multiline string describing each move in human-readable form.
- Logic:
- Splits each log entry into from, to, and disc.
- Formats first move with "Move disc X from A to B,".
- Subsequent moves formatted with shorthand like "then A 2→ C," etc.
- Inserts line breaks and commas for readability.
- Input: Receives logs from recursive sub-workflow results (`A B C`).
- Output: Provides final `solution` string output.
- Edge cases: Empty logs or malformed entries may cause formatting issues.
---
#### 2.5 Supporting Sticky Notes (Documentation)
- **Overview:**
Sticky notes provide contextual explanations, input/output expectations, and algorithm details to assist users understanding the workflow.
- **Nodes Involved:**
- user input
- setup
- result
- solution
- swap
- move
- update
- Towers of Hanoi (main description note)
- **Node Details:**
- These nodes are purely informational with no execution role.
- Contain sample inputs, outputs, algorithm description, and step explanations.
- Provide links to further resources such as GitHub and Wikipedia on recursion.
---
### 3. Summary Table
| Node Name | Node Type | Functional Role | Input Node(s) | Output Node(s) | Sticky Note |
|---------------------|-------------------------|------------------------------------|-----------------------|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
| Start | Manual Trigger | Starts the workflow manually | - | Set number of discs | |
| Set number of discs | Set | Sets initial numberOfDiscs (4) | Start | Create stacks | |
| Create stacks | Code | Validates disc count and initializes stacks A, B, C with discs on A | Set number of discs | A B C | |
| A B C | Execute Workflow | Recursive call to main workflow with stacks and logs | Create stacks | Max 1 disc | |
| Max 1 disc | If | Checks base case (n ≤ 1) | A B C | X to Z (true), X Z Y (false) | |
| X to Z | Code | Moves one disc from stackX to stackZ and logs move | Max 1 disc (true) | Y X Z | |
| Y X Z | Execute Workflow | Recursive call with stacks reordered after move | X to Z | Update | |
| Update | Code | Restores stacks & increments numberOfDiscs after Y X Z | Y X Z | X to Z | "### Node steps\n- **numberOfDiscs** += 1\n- YXZ to XYZ\n - stack = **stackY**\n - **stackY** = **stackX**\n - **stackX** = stack" |
| X Z Y | Execute Workflow | Recursive call with stacks reordered before move | Max 1 disc (false) | Update | |
| Update | Code | Restores stacks & increments numberOfDiscs after X Z Y | X Z Y | X to Z | "### Node steps\n- **numberOfDiscs** += 1\n- Reset stacks (XZY to XYZ)\n - stack = **stackY**\n - **stackY** = **stackZ**\n - **stackZ** = stack" |
| X to Z | Code | Moves disc from stackX to stackZ and logs move | Update | Y X Z | "### Node steps\n- disc = **stackX**.pop()\n- **stackZ**.push(disc)" |
| Solution | Code | Formats logs into human-readable solution string | A B C | - | |
| user input | Sticky Note | Describes input: numberOfDiscs = 4 | - | - | "### Input\n- **numberOfDiscs:** 4 " |
| setup | Sticky Note | Describes initial stacks and discs | - | - | "### Setup\n- **numberOfDiscs:** 4 \n- **stackA**\n - **name:** A \n - **items:** 4, 3, 2, 1 \n- **stackB**\n - **name:** B \n - **items:** - \n- **stackC**\n - **name:** C \n - **items:** - \n" |
| result | Sticky Note | Shows expected final stacks after solving | - | - | "### Result\n- **numberOfDiscs:** 4 \n- **stackX**\n - **name:** A \n - **items:** - \n- **stackY**\n - **name:** B \n - **items:** - \n- **stackZ**\n - **name:** C \n - **items:** 4, 3, 2, 1" |
| solution | Sticky Note | Example formatted solution output | - | - | "### Solution\nMove disc 1 from A to B,\nthen A 2→ C,\nB 1→ C, A 3→ B,\nC 1→ A, C 2→ B,\nA 1→ B, A 4→ C,\nB 1→ C, B 2→ A,\nC 1→ A, B 3→ C,\nA 1→ B, A 2→ C,\nB 1→ C." |
| swap | Sticky Note | Explains input stack swaps in recursion | - | - | "### Node input\n- **numberOfDiscs** -1 \n- stackX = **stackX** \n- stackY = **stackZ** \n- stackZ = **stackY** " |
| move | Sticky Note | Explains disc move operations | - | - | "### Node steps\n- disc = **stackX**.pop()\n- **stackZ**.push(disc)" |
| update | Sticky Note | Explains stack resetting steps | - | - | "### Node steps\n- **numberOfDiscs** += 1\n- YXZ to XYZ\n - stack = **stackY**\n - **stackY** = **stackX**\n - **stackX** = stack" |
| Towers of Hanoi | Sticky Note | Full algorithm description, notes, and resource links | - | - | See section 5 for full content and links. |
---
### 4. Reproducing the Workflow from Scratch
1. **Create Manual Trigger Node ("Start")**
- Type: Manual Trigger
- No configuration needed.
2. **Create Set Node ("Set number of discs")**
- Set parameter: `numberOfDiscs` = 4 (Number type)
- Connect "Start" → "Set number of discs".
3. **Create Code Node ("Create stacks")**
- JavaScript:
- Enforce min 1 and max 5 discs.
- Initialize `stackA` with discs [numberOfDiscs ... 1].
- Initialize empty `stackB` and `stackC`.
- Initialize empty `logs` array.
- Connect "Set number of discs" → "Create stacks".
4. **Create Execute Workflow Node ("A B C")**
- Set to call same workflow (recursive call).
- Pass inputs:
- `numberOfDiscs`
- `stackX` = `stackA`
- `stackY` = `stackB`
- `stackZ` = `stackC`
- `logs`
- Enable "Wait for sub-workflow" to true.
- Connect "Create stacks" → "A B C".
5. **Create If Node ("Max 1 disc")**
- Condition: `numberOfDiscs <= 1` (number comparison)
- Connect "A B C" → "Max 1 disc".
6. **Create Code Node ("X to Z")**
- JavaScript:
```js
const disc = $json.stackX.items.pop();
$json.stackZ.items.push(disc);
$json.logs.push(`${$json.stackX.name} ${$json.stackZ.name} ${disc}`);
return $input.item;
```
- Connect "Max 1 disc" True branch → "X to Z".
7. **Create Execute Workflow Node ("Y X Z")**
- Set to call same workflow recursively.
- Pass inputs:
- `numberOfDiscs` = current `numberOfDiscs - 1`
- `stackX` = previous `stackY`
- `stackY` = previous `stackX`
- `stackZ` = previous `stackZ`
- `logs`
- Wait for sub-workflow result.
- Connect "X to Z" → "Y X Z".
8. **Create Code Node ("Update")**
- JavaScript:
```js
$json.numberOfDiscs += 1;
const stack = $json.stackY;
$json.stackY = $json.stackX;
$json.stackX = stack;
return $input.item;
```
- Connect "Y X Z" → "Update".
9. **Create Code Node (" X to Z")** (note the leading space in name)
- Same code as first "X to Z" node for moving disc and logging.
- Connect "Update" → " X to Z".
10. **Create Execute Workflow Node ("X Z Y")**
- Set to call same workflow recursively.
- Pass inputs:
- `numberOfDiscs` = current `numberOfDiscs - 1`
- `stackX` = previous `stackX`
- `stackY` = previous `stackZ`
- `stackZ` = previous `stackY`
- `logs`
- Wait for sub-workflow result.
- Connect "Max 1 disc" False branch → "X Z Y".
11. **Create Code Node ("Update")** (second instance)
- JavaScript:
```js
$json.numberOfDiscs += 1;
const stack = $json.stackY;
$json.stackY = $json.stackZ;
$json.stackZ = stack;
return $input.item;
```
- Connect "X Z Y" → this "Update".
12. **Connect "Update" → " X to Z"** (the space-prefixed node).
13. **Create Code Node ("Solution")**
- JavaScript:
```js
function logsToSentence(logs) {
return logs.map((entry, index) => {
const [from, to, disc] = entry.split(" ");
let text;
if (index === 0) {
text = `Move disc ${disc} from ${from} to ${to}`;
} else if (index === 1) {
text = `then ${from} ${disc}→ ${to}`;
} else {
text = `${from} ${disc}→ ${to}`;
}
text += ",";
if (index === 0 || index % 2 === 1) {
text += "\n";
} else {
text += " ";
}
return text;
}).join("").slice(0, -2) + ".";
}
return { solution: logsToSentence($input.first().json.logs) };
```
- Connect "A B C" → "Solution".
14. **Add Sticky Notes** for documentation as needed.
---
### 5. General Notes & Resources
| Note Content | Context or Link |
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ## Towers of Hanoi
This workflow demonstrates recursion in n8n using sub-workflows to solve the Towers of Hanoi puzzle.
### Algorithm
```procedure Hanoi(n, X, Y, Z):
if n == 1:
move disk from X to Z
else:
Hanoi(n-1, X, Z, Y)
move disk from X to Z
Hanoi(n-1, Y, X, Z)
```
Notes:
- Variables X, Y, Z represent rods; swapping them adjusts positions.
- Recursive calls return updated stacks, requiring stack resetting nodes.
- Termination condition is important to avoid infinite recursion.
- Links:
- [n8n Dashboard Restart workspace](https://app.n8n.cloud/manage)
- [GitHub Workflow repo](https://github.com/adibaba/n8n.workflows)
- [Recursion Wikipedia (Towers of Hanoi)](https://en.wikipedia.org/w/index.php?title=Recursion_(computer_science)&oldid=1301600240#Towers_of_Hanoi) | Main workflow sticky note with full description and resources. |
---
**Disclaimer:** The provided text is solely derived from an automated workflow created using n8n, a workflow automation tool. This processing strictly adheres to content policies and contains no illegal, offensive, or protected material. All data handled is legal and public.