diff --git a/README.md b/README.md
index d27a115..a66bec4 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,9 @@ Times are from my home machine: AMD Ryzen 7900X3D, 64GB DDR5 RAM @ 6000MHz
 $ julia --project examples/import_bench.jl
 AB->AB:
 Graph:
-  Nodes: Total: 34, DataTask: 19, ComputeTaskP: 4, ComputeTaskS2: 2, ComputeTaskV: 4, ComputeTaskU: 4, ComputeTaskSum: 1
+  Nodes: Total: 34, DataTask: 19, ComputeTaskP: 4, 
+         ComputeTaskS2: 2, ComputeTaskV: 4, ComputeTaskU: 4, 
+         ComputeTaskSum: 1
   Edges: 37
   Total Compute Effort: 185
   Total Data Transfer: 102
@@ -43,7 +45,9 @@ Graph:
 
 AB->ABBB:
 Graph:
-  Nodes: Total: 280, DataTask: 143, ComputeTaskP: 6, ComputeTaskS2: 24, ComputeTaskV: 64, ComputeTaskU: 6, ComputeTaskSum: 1, ComputeTaskS1: 36
+  Nodes: Total: 280, DataTask: 143, ComputeTaskP: 6, 
+         ComputeTaskS2: 24, ComputeTaskV: 64, ComputeTaskU: 6, 
+         ComputeTaskSum: 1, ComputeTaskS1: 36
   Edges: 385
   Total Compute Effort: 2007
   Total Data Transfer: 828
@@ -53,7 +57,9 @@ Graph:
 
 AB->ABBBBB:
 Graph:
-  Nodes: Total: 7854, DataTask: 3931, ComputeTaskP: 8, ComputeTaskS2: 720, ComputeTaskV: 1956, ComputeTaskU: 8, ComputeTaskSum: 1, ComputeTaskS1: 1230
+  Nodes: Total: 7854, DataTask: 3931, ComputeTaskP: 8, 
+         ComputeTaskS2: 720, ComputeTaskV: 1956, ComputeTaskU: 8, 
+         ComputeTaskSum: 1, ComputeTaskS1: 1230
   Edges: 11241
   Total Compute Effort: 58789
   Total Data Transfer: 23244
@@ -72,7 +78,9 @@ Graph:
 
 AB->ABBBBBBB:
 Graph:
-  Nodes: Total: 438436, DataTask: 219223, ComputeTaskP: 10, ComputeTaskS2: 40320, ComputeTaskV: 109600, ComputeTaskU: 10, ComputeTaskSum: 1, ComputeTaskS1: 69272
+  Nodes: Total: 438436, DataTask: 219223, ComputeTaskP: 10, 
+         ComputeTaskS2: 40320, ComputeTaskV: 109600, ComputeTaskU: 10, 
+         ComputeTaskSum: 1, ComputeTaskS1: 69272
   Edges: 628665
   Total Compute Effort: 3288131
   Total Data Transfer: 1297700
@@ -80,9 +88,20 @@ Graph:
   Graph size in memory: 118.4037 MiB
   463.082 ms (7645256 allocations: 422.57 MiB)
 
+AB->ABBBBBBBBB:
+Graph:
+  Nodes: Total: 39456442, DataTask: 19728227, ComputeTaskS1: 6235290, ComputeTaskP: 12, ComputeTaskU: 12, ComputeTaskV: 9864100, ComputeTaskS2: 3628800, ComputeTaskSum: 1
+  Edges: 56578129
+  Total Compute Effort: 295923153
+  Total Data Transfer: 175407750
+  Total Compute Intensity: 1.6870585991782006
+  Graph size in memory: 12.7845 GiB
+
 ABAB->ABAB:
 Graph:
-  Nodes: Total: 3218, DataTask: 1613, ComputeTaskP: 8, ComputeTaskS2: 288, ComputeTaskV: 796, ComputeTaskU: 8, ComputeTaskSum: 1, ComputeTaskS1: 504
+  Nodes: Total: 3218, DataTask: 1613, ComputeTaskP: 8, 
+         ComputeTaskS2: 288, ComputeTaskV: 796, ComputeTaskU: 8, 
+         ComputeTaskSum: 1, ComputeTaskS1: 504
   Edges: 4581
   Total Compute Effort: 24009
   Total Data Transfer: 9494
@@ -92,11 +111,13 @@ Graph:
 
 ABAB->ABC:
 Graph:
-  Nodes: Total: 817, DataTask: 412, ComputeTaskP: 7, ComputeTaskS2: 72, ComputeTaskV: 198, ComputeTaskU: 7, ComputeTaskSum: 1, ComputeTaskS1: 120
+  Nodes: Total: 817, DataTask: 412, ComputeTaskP: 7,
+         ComputeTaskS2: 72, ComputeTaskV: 198, ComputeTaskU: 7,
+         ComputeTaskSum: 1, ComputeTaskS1: 120
   Edges: 1151
   Total Compute Effort: 6028
   Total Data Transfer: 2411
   Total Compute Intensity: 2.5002073828287017
   Graph size in memory: 225.0625 KiB
   286.583 μs (13996 allocations: 804.48 KiB)
-```
\ No newline at end of file
+```
diff --git a/src/graph.jl b/src/graph.jl
index d56fc4b..d2bc808 100644
--- a/src/graph.jl
+++ b/src/graph.jl
@@ -54,15 +54,13 @@ mutable struct PossibleOperations
    nodeFusions::Set{NodeFusion}
    nodeReductions::Set{NodeReduction}
    nodeSplits::Set{NodeSplit}
-   dirty::Bool
 end
 
 function PossibleOperations()
    return PossibleOperations(
       Set{NodeFusion}(),
       Set{NodeReduction}(),
-      Set{NodeSplit}(),
-      true
+      Set{NodeSplit}()
    )
 end
 
@@ -81,11 +79,14 @@ mutable struct DAG
    # The possible operations at the current state of the DAG
    possibleOperations::PossibleOperations
 
+   # The set of nodes whose possible operations need to be reevaluated
+   dirtyNodes::Set{Node}
+
    # "snapshot" system: keep track of added/removed nodes/edges since last snapshot
    # these are muted in insert_node! etc.
    diff::Diff
 end
 
 function DAG()
-   return DAG(Set{Node}(), Stack{AppliedOperation}(), Deque{Operation}(), PossibleOperations(), Diff())
+   return DAG(Set{Node}(), Stack{AppliedOperation}(), Deque{Operation}(), PossibleOperations(), Set{Node}(), Diff())
 end
diff --git a/src/graph_functions.jl b/src/graph_functions.jl
index f8ddd44..2225508 100644
--- a/src/graph_functions.jl
+++ b/src/graph_functions.jl
@@ -63,38 +63,100 @@ end
 is_entry_node(node::Node) = length(children(node)) == 0
 is_exit_node(node::Node) = length(parents(node)) == 0
 
+# function to invalidate the operation caches for a given operation
+function invalidate_caches!(graph::DAG, operation::Operation)
+   delete!(graph.possibleOperations, operation)
+
+   # delete the operation from all caches of nodes involved in the operation
+   # (we can iterate over single values, tuples and vectors just fine)
+   for node in operation.input
+      delete!(node.operations, operation)
+   end
+
+   return nothing
+end
+
+# for graph mutating functions we need to do a few things
+# 1: mute the graph (duh)
+# 2: keep track of what was changed for the diff (if track == true)
+# 3: invalidate operation caches
+
 function insert_node!(graph::DAG, node::Node, track=true)
+   # 1: mute
    push!(graph.nodes, node)
 
+   # 2: keep track
    if (track) push!(graph.diff.addedNodes, node) end
-   graph.possibleOperations.dirty = true
+
+   # 3: invalidate caches
+   push!(graph.dirtyNodes, node)
+
    return node
 end
 
 function insert_edge!(graph::DAG, edge::Edge, track=true)
-   # edge points from child to parent
-   push!(edge.edge[1].parents, edge.edge[2])
-   push!(edge.edge[2].children, edge.edge[1])
+   node1 = edge.edge[1]
+   node2 = edge.edge[2]
 
+   # 1: mute
+   # edge points from child to parent
+   push!(node1.parents, node2)
+   push!(node2.children, node1)
+
+   # 2: keep track
    if (track) push!(graph.diff.addedEdges, edge) end
-   graph.possibleOperations.dirty = true
+   
+   # 3: invalidate caches
+   while !isempty(node1.operations)
+      invalidate_caches!(graph, first(node1.operations))
+   end
+   while !isempty(node2.operations)
+      invalidate_caches!(graph, first(node2.operations))
+   end
+   push!(graph.dirtyNodes, node1)
+   push!(graph.dirtyNodes, node2)
+
    return edge
 end
 
 function remove_node!(graph::DAG, node::Node, track=true)
+   # 1: mute
    delete!(graph.nodes, node)
 
+   # 2: keep track
    if (track) push!(graph.diff.removedNodes, node) end
-   graph.possibleOperations.dirty = true
+
+   # 3: invalidate caches
+   while !isempty(node)
+      invalidate_caches!(graph, first(node.operations))
+   end
+   delete!(graph.dirtyNodes, node)
+   # no need to invalidate anything else, the node is gone afterwards anyways
+
    return nothing
 end
 
 function remove_edge!(graph::DAG, edge::Edge, track=true)
-   filter!(x -> x != edge.edge[2], edge.edge[1].parents)
-   filter!(x -> x != edge.edge[1], edge.edge[2].children)
+   node1 = edge.edge[1]
+   node2 = edge.edge[2]
 
+   # 1: mute
+   filter!(x -> x != node2, node1.parents)
+   filter!(x -> x != node1, node2.children)
+
+   # 2: keep track
    if (track) push!(graph.diff.removedEdges, edge) end
-   graph.possibleOperations.dirty = true
+
+   # 3: invalidate caches
+   while !isempty(node1.operations)
+      invalidate_caches!(graph, first(node1.operations))
+   end
+   while !isempty(node2.operations)
+      invalidate_caches!(graph, first(node2.operations))
+   end
+   push!(graph.dirtyNodes, node1)
+   push!(graph.dirtyNodes, node2)
+
    return nothing
 end
 
@@ -134,6 +196,10 @@ function get_exit_node(graph::DAG)
    error("The given graph has no exit node! It is either empty or not acyclic!")
 end
 
+function can_fuse(n1::ComputeTaskNode, n2::DataTaskNode, n3::ComputeTaskNode)
+   #Todo
+end
+
 function can_reduce(n1::Node, n2::Node)
    if (n1.task != n2.task)
       return false
@@ -198,12 +264,17 @@ function show(io::IO, graph::DAG)
    else
       print("Total: ", length(graph.nodes), ", ")
       first = true
+      i = 0
       for (type, number) in zip(keys(nodeDict), values(nodeDict))
+         i += 1
          if first
             first = false
          else
             print(", ")
          end
+         if (i % 3 == 0)
+            print("\n         ")
+         end
          print(type, ": ", number)
       end
    end
diff --git a/src/graph_operations.jl b/src/graph_operations.jl
index bac0285..18a3414 100644
--- a/src/graph_operations.jl
+++ b/src/graph_operations.jl
@@ -5,12 +5,7 @@ function push_operation!(graph::DAG, operation::Operation)
    # 1.: Add the operation to the DAG
    push!(graph.operationsToApply, operation)
 
-   # 2.: Apply all operations in the chain
-   apply_all!(graph)
-
-   # 3.: Regenerate properties, possible operations from here
-
-   graph.possibleOperations.dirty = true
+   return nothing
 end
 
 # reverts the latest applied operation, essentially like a ctrl+z for
@@ -24,13 +19,7 @@ function pop_operation!(graph::DAG)
    else
       error("No more operations to pop!")
    end
-
-   # 2.: Apply all (remaining) operations in the chain
-   apply_all!(graph)
-
-   # 3.: Regenerate properties, possible operations from here
-
-   graph.possibleOperations.dirty = true
+   return nothing
 end
 
 can_pop(graph::DAG) = !isempty(graph.operationsToApply) || !isempty(graph.appliedOperations)
@@ -40,6 +29,8 @@ function reset_graph!(graph::DAG)
    while (can_pop(graph))
       pop_operation!(graph)
    end
+
+   return nothing
 end
 
 # implementation detail functions, don't export
@@ -56,6 +47,7 @@ function apply_all!(graph::DAG)
       # push to the end of the appliedOperations deque
       push!(graph.appliedOperations, appliedOp)
    end
+   return nothing
 end
 
 
@@ -245,6 +237,11 @@ function node_split!(graph::DAG, n1::Node)
    return get_snapshot_diff(graph)
 end
 
+# function to find node fusions involving the given node
+function find_fusions(graph::DAG, node::Node)
+   
+end
+
 # function to generate all possible optmizations on the graph
 function generate_options(graph::DAG)
    options = PossibleOperations()
diff --git a/src/import.jl b/src/import.jl
index 9438946..d4be7e1 100644
--- a/src/import.jl
+++ b/src/import.jl
@@ -20,7 +20,7 @@ function parse_edges(input::AbstractString)
     return output
 end
 
-function import_txt(filename::String, verbose::Bool = isinteractive())
+function import_txt(filename::String, verbose::Bool = false)
     file = open(filename, "r")
 
     if (verbose) println("Opened file") end