diff --git a/packages/desktop-client/src/components/settings/Experimental.tsx b/packages/desktop-client/src/components/settings/Experimental.tsx
index 41fe8d6e95dde871f95a812883397ca59cd58220..4b809341ff584f8677efb83055f4263e3f9914c0 100644
--- a/packages/desktop-client/src/components/settings/Experimental.tsx
+++ b/packages/desktop-client/src/components/settings/Experimental.tsx
@@ -90,6 +90,9 @@ export function ExperimentalFeatures() {
               Goal templates
             </FeatureToggle>
             <FeatureToggle flag="simpleFinSync">SimpleFIN sync</FeatureToggle>
+            <FeatureToggle flag="iterableTopologicalSort">
+              Iterable topological sort budget
+            </FeatureToggle>
           </View>
         ) : (
           <Link
diff --git a/packages/desktop-client/src/hooks/useFeatureFlag.ts b/packages/desktop-client/src/hooks/useFeatureFlag.ts
index 010b172ec0aec5727aaabd3452d4e23de1f8f9c0..0089686be590106432082c79a12aa960267d58fe 100644
--- a/packages/desktop-client/src/hooks/useFeatureFlag.ts
+++ b/packages/desktop-client/src/hooks/useFeatureFlag.ts
@@ -9,6 +9,7 @@ const DEFAULT_FEATURE_FLAG_STATE: Record<FeatureFlag, boolean> = {
   customReports: false,
   spendingReport: false,
   simpleFinSync: false,
+  iterableTopologicalSort: true,
 };
 
 export function useFeatureFlag(name: FeatureFlag): boolean {
diff --git a/packages/loot-core/src/server/spreadsheet/graph-data-structure.ts b/packages/loot-core/src/server/spreadsheet/graph-data-structure.ts
index 76fe24762156fe7ddd228411d77e6376ddd33671..be2669d1ff2d82daea376d2d63433877503329ba 100644
--- a/packages/loot-core/src/server/spreadsheet/graph-data-structure.ts
+++ b/packages/loot-core/src/server/spreadsheet/graph-data-structure.ts
@@ -1,3 +1,5 @@
+import { getPrefs } from '../prefs';
+
 // @ts-strict-ignore
 export function Graph() {
   const graph = {
@@ -76,14 +78,38 @@ export function Graph() {
     return graph;
   }
 
-  function topologicalSortUntil(name, visited, sorted) {
+  function topologicalSort(sourceNodes) {
+    const visited = new Set();
+    const sorted = [];
+    const prefs = getPrefs();
+    const iterableTopologicalSort =
+      prefs != null ? prefs['flags.iterableTopologicalSort'] : false;
+
+    sourceNodes.forEach(name => {
+      if (!visited.has(name)) {
+        if (iterableTopologicalSort) {
+          topologicalSortIterable(name, visited, sorted);
+        } else {
+          topologicalSortUntil(name, visited, sorted, 0);
+        }
+      }
+    });
+
+    return sorted;
+  }
+
+  function topologicalSortUntil(name, visited, sorted, level) {
     visited.add(name);
+    if (level > 2500) {
+      console.error('Limit of recursions reached while sorting budget: 2500');
+      return;
+    }
 
     const iter = adjacent(name).values();
     let cur = iter.next();
     while (!cur.done) {
       if (!visited.has(cur.value)) {
-        topologicalSortUntil(cur.value, visited, sorted);
+        topologicalSortUntil(cur.value, visited, sorted, level + 1);
       }
       cur = iter.next();
     }
@@ -91,17 +117,54 @@ export function Graph() {
     sorted.unshift(name);
   }
 
-  function topologicalSort(sourceNodes) {
-    const visited = new Set();
-    const sorted = [];
+  function topologicalSortIterable(name, visited, sorted) {
+    const stackTrace: StackItem[] = [];
 
-    sourceNodes.forEach(name => {
-      if (!visited.has(name)) {
-        topologicalSortUntil(name, visited, sorted);
-      }
+    stackTrace.push({
+      count: -1,
+      value: name,
+      parent: '',
+      level: 0,
     });
 
-    return sorted;
+    while (stackTrace.length > 0) {
+      const current = stackTrace.slice(-1)[0];
+
+      const adjacents = adjacent(current.value);
+      if (current.count === -1) {
+        current.count = adjacents.size;
+      }
+
+      if (current.count > 0) {
+        const iter = adjacents.values();
+        let cur = iter.next();
+        while (!cur.done) {
+          if (!visited.has(cur.value)) {
+            stackTrace.push({
+              count: -1,
+              parent: current.value,
+              value: cur.value,
+              level: current.level + 1,
+            });
+          } else {
+            current.count--;
+          }
+          cur = iter.next();
+        }
+      } else {
+        if (!visited.has(current.value)) {
+          visited.add(current.value);
+          sorted.unshift(current.value);
+        }
+
+        const removed = stackTrace.pop();
+        for (let i = 0; i < stackTrace.length; i++) {
+          if (stackTrace[i].value === removed.parent) {
+            stackTrace[i].count--;
+          }
+        }
+      }
+    }
   }
 
   function generateDOT() {
@@ -113,11 +176,18 @@ export function Graph() {
     });
 
     return `
-      digraph G {
-       ${edgeStrings.join('\n').replace(/!/g, '_')}
-      }
+    digraph G {
+      ${edgeStrings.join('\n').replace(/!/g, '_')}
+    }
     `;
   }
 
   return graph;
 }
+
+interface StackItem {
+  count: number;
+  value: string;
+  parent: string;
+  level: number;
+}
diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts
index 161c85faa54b6d2ce277e369a6a73c2a4031618e..38f7b1a972d5df65f2f337db67af2cb7af11a714 100644
--- a/packages/loot-core/src/types/prefs.d.ts
+++ b/packages/loot-core/src/types/prefs.d.ts
@@ -5,7 +5,8 @@ export type FeatureFlag =
   | 'goalTemplatesEnabled'
   | 'customReports'
   | 'spendingReport'
-  | 'simpleFinSync';
+  | 'simpleFinSync'
+  | 'iterableTopologicalSort';
 
 export type LocalPrefs = Partial<
   {
diff --git a/upcoming-release-notes/2848.md b/upcoming-release-notes/2848.md
new file mode 100644
index 0000000000000000000000000000000000000000..35fc24299f1f6bedf8eaa74af2a1c0451c008a79
--- /dev/null
+++ b/upcoming-release-notes/2848.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [lelemm]
+---
+
+Remove recursion from topological sort to prevent stack overflow