diff --git a/README.md b/README.md
index 6e1119df..c64ef099 100644
--- a/README.md
+++ b/README.md
@@ -611,14 +611,17 @@ visitor.mutate("IfNode[predicate: Assign | OpAssign]") do |node|
   node.copy(predicate: predicate)
 end
 
-source = "if a = 1; end"
+# remove `do_more_work` method call node
+visitor.remove("SyntaxTree::VCall[value: SyntaxTree::Ident[value: 'do_more_work']]")
+
+source = "if a = 1; perform_work; do_more_work; end"
 program = SyntaxTree.parse(source)
 
 SyntaxTree::Formatter.format(source, program)
-# => "if a = 1\nend\n"
+# => "if a = 1\n  perform_work\n  do_more_work\nend\n"
 
 SyntaxTree::Formatter.format(source, program.accept(visitor))
-# => "if (a = 1)\nend\n"
+# => "if (a = 1)\n  perform_work\nend\n"
 ```
 
 ### WithScope
diff --git a/lib/syntax_tree/mutation_visitor.rb b/lib/syntax_tree/mutation_visitor.rb
index 0b4b9357..8390ca54 100644
--- a/lib/syntax_tree/mutation_visitor.rb
+++ b/lib/syntax_tree/mutation_visitor.rb
@@ -4,10 +4,11 @@ module SyntaxTree
   # This visitor walks through the tree and copies each node as it is being
   # visited. This is useful for mutating the tree before it is formatted.
   class MutationVisitor < BasicVisitor
-    attr_reader :mutations
+    attr_reader :mutations, :removals
 
     def initialize
       @mutations = []
+      @removals = []
     end
 
     # Create a new mutation based on the given query that will mutate the node
@@ -19,6 +20,10 @@ def mutate(query, &block)
       mutations << [Pattern.new(query).compile, block]
     end
 
+    def remove(query)
+      @removals << Pattern.new(query).compile
+    end
+
     # This is the base visit method for each node in the tree. It first creates
     # a copy of the node using the visit_* methods defined below. Then it checks
     # each mutation in sequence and calls it if it finds a match.
@@ -26,6 +31,12 @@ def visit(node)
       return unless node
       result = node.accept(self)
 
+      removals.each do |removal_pattern|
+        if removal_pattern.call(result)
+          return RemovedNode.new(location: result.location)
+        end
+      end
+
       mutations.each do |(pattern, mutation)|
         result = mutation.call(result) if pattern.call(result)
       end
diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb
index 3b676552..4401a7a1 100644
--- a/lib/syntax_tree/node.rb
+++ b/lib/syntax_tree/node.rb
@@ -9324,6 +9324,48 @@ def ambiguous?(q)
     end
   end
 
+  # RemovedNode is a blank node used in places of nodes that have been removed.
+  class RemovedNode < Node
+    # [Array[ Comment | EmbDoc ]] the comments attached to this node
+    attr_reader :comments
+
+    def initialize(location:)
+      @location = location
+      @comments = []
+    end
+
+    def accept(visitor)
+      visitor.visit_removed_node(self)
+    end
+
+    def child_nodes
+      []
+    end
+
+    def copy(location: self.location)
+      node = RemovedNode.new(
+        location: location
+      )
+
+      node.comments.concat(comments.map(&:copy))
+
+      node
+    end
+
+    alias deconstruct child_nodes
+
+    def deconstruct_keys(_keys)
+      { location: location, comments: comments }
+    end
+
+    def format(_q)
+    end
+
+    def ===(other)
+      other.is_a?(RemovedNode)
+    end
+  end
+
   # RescueEx represents the list of exceptions being rescued in a rescue clause.
   #
   #     begin
diff --git a/lib/syntax_tree/visitor.rb b/lib/syntax_tree/visitor.rb
index eb57acd2..294ddf9c 100644
--- a/lib/syntax_tree/visitor.rb
+++ b/lib/syntax_tree/visitor.rb
@@ -320,6 +320,9 @@ class Visitor < BasicVisitor
     # Visit a RegexpLiteral node.
     alias visit_regexp_literal visit_child_nodes
 
+    # Visit a RemovedNode node.
+    alias visit_removed_node visit_child_nodes
+
     # Visit a Rescue node.
     alias visit_rescue visit_child_nodes
 
diff --git a/test/mutation_test.rb b/test/mutation_test.rb
index ab9dd019..870a9e0a 100644
--- a/test/mutation_test.rb
+++ b/test/mutation_test.rb
@@ -21,6 +21,35 @@ def test_mutates_based_on_patterns
       assert_equal(expected, SyntaxTree::Formatter.format(source, program))
     end
 
+    def test_removes_node
+      source = <<~RUBY
+        App.configure do |config|
+          config.config_value_a = 1
+          config.config_value_b = 2
+          config.config_value_c = 2
+        end
+      RUBY
+
+      expected = <<~RUBY
+        App.configure do |config|
+          config.config_value_a = 1
+
+          config.config_value_c = 2
+        end
+      RUBY
+
+      mutation_visitor = SyntaxTree.mutation do |mutation|
+        mutation.remove("SyntaxTree::Assign[
+          target: SyntaxTree::Field[
+            name: SyntaxTree::Ident[value: 'config_value_b']
+          ],
+        ]")
+      end
+
+      program = SyntaxTree.parse(source).accept(mutation_visitor)
+      assert_equal(expected, SyntaxTree::Formatter.format(source, program))
+    end
+
     private
 
     def build_mutation