Another Loop issue - 2.000 items in ... 21.000 items out

Describe the problem/error/question

I have a Loop within a Loop. My workflow creates 2.000 items and the outer loop batch size is 100. The inner loop has a batch size of 1. The first batch run of the outer loop gets processed as expected, but the second loop is acting very weird. At the end I get 21.000 Items out of the outer loop. Whjat is wrong here or is this a bug ?

What is the error message (if any)?

Please share your workflow

Share the output returned by the last node

Information on your n8n setup

  • n8n version: 2.23.2 Community Edition
  • Database (default: SQLite):
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • Running n8n via (Docker, npm, n8n cloud, desktop app):
  • Operating system: Ubuntu 22.04
  1. Disconnect the connection going from Outer Loop back to Outer Loop Over Items.
  2. Place a Code node immediately after your inner loop finishes (on the “Done” branch), right before it circles back to the outer loop.
  3. Use this code node to pass the data through, but configure the inner loop to reset. In newer versions of the Split In Batches / Looping node, you can toggle the Reset option to true.
    Hope this helps

Hi @Nadir

The problem is that your workflow’s “wires” are connected to the wrong ports. In n8n, the Split in Batches node has two outputs: one for the items currently being processed (the Loop) and one for when everything is finished (the Done path). You have accidentally swapped these, meaning your workflow is trying to start the next step only after it’s already finished, and it’s looping back in ways it shouldn’t.

Because of these swapped connections, you’ve created a “feedback loop.” Instead of processing each item once, the workflow is triggering new cycles of the loop every time a batch completes. This causes the items to multiply exponentially, which is why your original 2,000 items snowballed into 21,000.

To fix this, ensure the top output (Loop) of the Outer Loop goes into the Inner Loop, and the top output of the Inner Loop handles your actual work. The bottom output (Done) of the Inner Loop should then point back to the Outer Loop to ask for the next batch. This ensures the inner loop finishes all its items before the outer loop moves forward.

Try this

Hi ! Thank you for taking the time to look at this.

My workflow after your advise - did I understand something wrong ? :

If I toggle the reset option on the Inner Loop Over Items I end up in an endless loop. I only get out of this be rebooting the N8N system.

{
“nodes”: [
{
“parameters”: {},
“type”: “n8n-nodes-base.manualTrigger”,
“typeVersion”: 1,
“position”: [
-144,
-64
],
“id”: “dded6d9b-9f8d-4227-8fc5-12159d18723b”,
“name”: “When clicking ‘Execute workflow’”
},
{
“parameters”: {},
“type”: “n8n-nodes-base.wait”,
“typeVersion”: 1.1,
“position”: [
912,
-64
],
“id”: “6bcb6624-8761-4f94-879c-2dc3cfb6fe7b”,
“name”: “Wait”,
“webhookId”: “f3fe0b63-6c82-49eb-8380-d60c3f1541c6”
},
{
“parameters”: {},
“type”: “n8n-nodes-base.noOp”,
“typeVersion”: 1,
“position”: [
672,
96
],
“id”: “d7112c4a-6db2-4a14-98d1-f5c218502e8a”,
“name”: “Inner loop”
},
{
“parameters”: {},
“type”: “n8n-nodes-base.noOp”,
“typeVersion”: 1,
“position”: [
1120,
128
],
“id”: “08aed799-7b15-4f38-be6e-35f74f432366”,
“name”: “Outer loop”
},
{
“parameters”: {
“options”: {
“reset”: true
}
},
“type”: “n8n-nodes-base.splitInBatches”,
“typeVersion”: 3,
“position”: [
448,
32
],
“id”: “acf6c806-01e4-4b38-8aac-14f488408e89”,
“name”: “Inner Loop Over Items”
},
{
“parameters”: {
“batchSize”: 100,
“options”: {}
},
“type”: “n8n-nodes-base.splitInBatches”,
“typeVersion”: 3,
“position”: [
240,
-32
],
“id”: “05ed47b8-8ecc-4313-8702-e3cf2ddb1adb”,
“name”: “Outer Loop Over Items”
},
{
“parameters”: {
“jsCode”: “const items = ;\n\nfor(let i = 1; i <= 2000; i++) {\n items.push({\n json: {\n id: i,\n }\n })\n}\n\nreturn items;”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
32,
-64
],
“id”: “8af1c78b-59a6-42df-a269-2f3c36dbcc5a”,
“name”: “Create Items”
},
{
“parameters”: {
“jsCode”: “// Loop over input items and add a new field called ‘myNewField’ to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
672,
-64
],
“id”: “7676d152-fae2-4a54-94ef-4b3c685b1ed6”,
“name”: “Code in JavaScript”
}
],
“connections”: {
“When clicking ‘Execute workflow’”: {
“main”: [
[
{
“node”: “Create Items”,
“type”: “main”,
“index”: 0
}
]
]
},
“Wait”: {
“main”: [
[
{
“node”: “Outer loop”,
“type”: “main”,
“index”: 0
}
]
]
},
“Inner loop”: {
“main”: [
[
{
“node”: “Inner Loop Over Items”,
“type”: “main”,
“index”: 0
}
]
]
},
“Outer loop”: {
“main”: [
[
{
“node”: “Outer Loop Over Items”,
“type”: “main”,
“index”: 0
}
]
]
},
“Inner Loop Over Items”: {
“main”: [
[
{
“node”: “Code in JavaScript”,
“type”: “main”,
“index”: 0
}
],
[
{
“node”: “Inner loop”,
“type”: “main”,
“index”: 0
}
]
]
},
“Outer Loop Over Items”: {
“main”: [
,
[
{
“node”: “Inner Loop Over Items”,
“type”: “main”,
“index”: 0
}
]
]
},
“Create Items”: {
“main”: [
[
{
“node”: “Outer Loop Over Items”,
“type”: “main”,
“index”: 0
}
]
]
},
“Code in JavaScript”: {
“main”: [
[
{
“node”: “Wait”,
“type”: “main”,
“index”: 0
}
]
]
}
},
“pinData”: {},
“meta”: {
“instanceId”: “3a5f2016f235f0058559dcaa60a889f9bd210e06a6a8d1adec8c4d5747d8d207”
}
}

I am extremely sorry, you can use the solution given by kjooleng or I have another solution which is to create another workflow.
Create a Second Workflow:

  • Start with an Execute Workflow Trigger.

  • Connect it to your Split In Batches (Batch size: 1).

  • Put your Inner loop logic inside here.

  • When this workflow finishes, it naturally destroys its state, meaning it is perfectly fresh every time it is called.

  1. Modify your Main Workflow (The Outer Loop):
    • Keep your Outer Loop Over Items (Batch size: 100).

    • Inside the outer loop, instead of using your old inner loop nodes, use an Execute Workflow node that calls your second workflow.

    • Pass the current 100-item batch into it.

This doesn’t work at all. The outer Loop will forward the first batch via the loop output to the Outer Loop Node and as there is no way back to the Outer Loop over Items nothing happens. The Inner Loop will never be reached by this setup.

Maybe I should explain why I need this setup. I have over 20,000 items and need to send them via HTTP request to an external API. This external API can handle only 999 items max per call. So I need to process the 20,000 items in batches. The inner loop has to translate the items (max 999) into the right format and has to call the API.

@SE-automations I don’t like it, but I will give it a try with a sub-workflow.

Hi @Nadir

Perhaps this is what you want

@kjooleng The HTTP Request is giving back a result XML document with 999 items in it. After the HTTP Request I transform the XML into JSON with 999 items. And now I need the inner loop to loop over these 999 items before I process the next batch process (next 999 items). Basically I need to analyse the result of the HTTP Request before I trigger the next HPPT Request. That is why I need the inner loop as part of the outer loop. I will give it a try with the sub-workflow.

However - I think it is a bug in the Loop node.

Putting the inner loop into its own workflow and call it within the outer loop does work.

Happy that you got the solution :smiley:

Looking at your workflow the problem is clear. Your Inner loop’s output is connecting back into the Outer loop node, which means every time the inner loop finishes a batch it’s feeding its results back into the outer loop’s item stream. That’s exactly why you’re ending up with 21,000 items instead of 2,000.

The connection from Inner loop back to Outer Loop Over Items should not be there. The only thing that should feed back into the Outer Loop Over Items node is the loop connection itself, not the processed output.

Here’s how to fix it:

Remove the connection going from the Inner loop output back into the Outer Loop Over Items node.

The Outer loop output node should be your final output, whatever comes out of there is your end result after all batches are processed.

If you need to collect all results at the end, add a Merge node after the Outer loop output to consolidate everything cleanly.

Basically your outer loop should drive the batches, the inner loop should process each item within that batch, and the results should flow forward to the Outer loop output , not loop back into the beginning of the outer loop again.

Fix that connection and your item count should come out exactly right.

@akingbade-Samuel Hi ! I don’t know what you mean. The only loop back to the Outer Loop over Items is comping from the Inner Loop over Items done output.

The item multiplication happens because the inner Loop Over Items accumulates items across its batches internally, and when its “Done” output fires, n8n passes all accumulated items back to the outer loop’s input. Each outer batch iteration then carries a growing pile of items from previous runs.

The fix: on your inner Loop Over Items node, enable the “Reset” option. This clears the inner loop’s internal counter each time it’s called by the outer loop, so each outer batch starts fresh with only its own items, not the cumulative total from all previous batches.

If Reset is already on and the issue persists, share a screenshot of both loop node settings - that’ll help narrow it down.

As I mentioned above, if I turn on the reset on the inner loop I end up in an endless inner loop that can only be stopped by rebooting the server. The solution with the sub-workflow works and it is basically the same wiring. The sub-workflow also returns 100 items and these are not getting aded to the outer loop.

@nguyenthieutoan Settings can been seen in the uploaded workflow. Just double-click the node.

Hey! This isn’t a bug it’s a known gotcha with nested loops in n8n.

The problem: When your inner loop finishes, it passes its items back to the outer loop on each iteration. So instead of the outer loop just moving to the next batch, it’s accumulating all the items from the inner loop runs which is why you’re ending up with 21,000 instead of 2,000.

The fix: Add a Summarize node or a Code node right before the inner loop feeds back into the outer loop to reset/clear the item count. Basically you need to make sure only the expected items are being passed back up.

Also double check that your inner loop’s “done” output is connected correctly it should only connect forward, not accidentally feeding back into the outer loop’s input.

@Ayomikun27 If I use a sub-workflow instead of the inner loop, that sub-workflow will give back 100 items as well. But these 100 items are not getting added to the outer loop. So it must be a bug.

{
“nodes”: [
{
“parameters”: {
“batchSize”: 999,
“options”: {}
},
“type”: “n8n-nodes-base.splitInBatches”,
“typeVersion”: 3,
“position”: [
3536,
1088
],
“id”: “1796ac9c-4527-4682-b7ce-be15fb995083”,
“name”: “Get 999 items at a time”
},
{
“parameters”: {
“url”: "your api ",
“options”: {}
},
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.4,
“position”: [
3760,
1168
],
“id”: “92c992a3-c11c-45d7-a24e-f3ab21507c26”,
“name”: “send HTTP req with 999 items”
},
{
“parameters”: {
“options”: {
“reset”: “={{ $prevNode.name ===‘send HTTP req with 999 items’}}”
}
},
“type”: “n8n-nodes-base.splitInBatches”,
“typeVersion”: 3,
“position”: [
3952,
1200
],
“id”: “81b34dbd-a826-4561-ad84-812a634dddfa”,
“name”: “loop over those 999 item’s response”
},
{
“parameters”: {},
“type”: “n8n-nodes-base.noOp”,
“typeVersion”: 1,
“position”: [
4176,
1296
],
“id”: “9c9bb1f7-0417-4fa1-b0b3-ce0e474e3d32”,
“name”: “Do your operation with them”
},
{
“parameters”: {
“jsCode”: “const items = ;\n\nfor(let i = 1; i <= 2000; i++) {\n items.push({\n json: {\n id: i,\n }\n })\n}\n\nreturn items;”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
3328,
1056
],
“id”: “3a6ef3b8-3c88-48ab-b64f-bcc20b17990b”,
“name”: “Get your data”
},
{
“parameters”: {
“options”: {}
},
“type”: “n8n-nodes-base.set”,
“typeVersion”: 3.4,
“position”: [
3744,
992
],
“id”: “21223cf6-52b9-430c-951b-84a1e2471274”,
“name”: “Done”
}
],
“connections”: {
“Get 999 items at a time”: {
“main”: [
[
{
“node”: “Done”,
“type”: “main”,
“index”: 0
}
],
[
{
“node”: “send HTTP req with 999 items”,
“type”: “main”,
“index”: 0
}
]
]
},
“send HTTP req with 999 items”: {
“main”: [
[
{
“node”: “loop over those 999 item’s response”,
“type”: “main”,
“index”: 0
}
]
]
},
“loop over those 999 item’s response”: {
“main”: [
[
{
“node”: “Get 999 items at a time”,
“type”: “main”,
“index”: 0
}
],
[
{
“node”: “Do your operation with them”,
“type”: “main”,
“index”: 0
}
]
]
},
“Do your operation with them”: {
“main”: [
[
{
“node”: “loop over those 999 item’s response”,
“type”: “main”,
“index”: 0
}
]
]
},
“Get your data”: {
“main”: [
[
{
“node”: “Get 999 items at a time”,
“type”: “main”,
“index”: 0
}
]
]
}
},
“pinData”: {},
“meta”: {
“instanceId”: “0f39d8fdd402ddce20d0eb724526828e8c2d8679170e3a08fe6cd471c799fab6”
}
}

Try this. Just use reset with the ‘expression’ mode and this statement in your inner loop. let know if this works.

The 21,000 number is the giveaway. That’s 100+200+300…+2000 a triangular number. What’s happening is that each time your inner loop finishes and feeds back into the outer loop, n8n accumulates all previously seen items instead of just the current batch. By the 20th outer iteration, you’re carrying 2,000 items through it.

This is a known behavior with nested SplitInBatches nodes — they share execution state in a way that causes items to stack up.

The fix: move your inner loop into a separate workflow and call it with the Execute Workflow node. Pass each outer batch as input, run the inner loop there, return the results. This completely isolates the two loop states.

Outer workflow: SplitInBatches (100) > Execute Workflow
Inner workflow: receives the 100 items, runs SplitInBatches (1) on them, returns results

It’s a bit more setup but it’s the reliable pattern for nested loops in n8n.