diff --git a/include/sway/desktop/transaction.h b/include/sway/desktop/transaction.h
index 7ab80eb8..cee4afed 100644
--- a/include/sway/desktop/transaction.h
+++ b/include/sway/desktop/transaction.h
@@ -6,34 +6,25 @@
 /**
  * Transactions enable us to perform atomic layout updates.
  *
- * When we want to make adjustments to the layout, we create a transaction.
- * A transaction contains a list of affected containers and their new state.
+ * A transaction contains a list of containers and their new state.
  * A state might contain a new size, or new border settings, or new parent/child
  * relationships.
  *
- * Calling transaction_commit() makes sway notify of all the affected clients
- * with their new sizes. We then wait for all the views to respond with their
- * new surface sizes. When all are ready, or when a timeout has passed, we apply
- * the updates all at the same time.
+ * Committing a transaction makes sway notify of all the affected clients with
+ * their new sizes. We then wait for all the views to respond with their new
+ * surface sizes. When all are ready, or when a timeout has passed, we apply the
+ * updates all at the same time.
+ *
+ * When we want to make adjustments to the layout, we change the pending state
+ * in containers, mark them as dirty and call transaction_commit_dirty(). This
+ * create and commits a transaction from the dirty containers.
  */
 
-struct sway_transaction;
-
 /**
- * Create a new transaction.
+ * Find all dirty containers, create and commit a transaction containing them,
+ * and unmark them as dirty.
  */
-struct sway_transaction *transaction_create(void);
-
-/**
- * Add a container's pending state to the transaction.
- */
-void transaction_add_container(struct sway_transaction *transaction,
-		struct sway_container *container);
-
-/**
- * Submit a transaction to the client views for configuration.
- */
-void transaction_commit(struct sway_transaction *transaction);
+void transaction_commit_dirty(void);
 
 /**
  * Notify the transaction system that a view is ready for the new layout.
diff --git a/include/sway/server.h b/include/sway/server.h
index a3e32898..a017d1c4 100644
--- a/include/sway/server.h
+++ b/include/sway/server.h
@@ -47,10 +47,7 @@ struct sway_server {
 	bool debug_txn_timings;
 
 	list_t *transactions;
-
-	// When a view is being destroyed and is waiting for a transaction to
-	// complete it will be stored here.
-	list_t *destroying_containers;
+	list_t *dirty_containers;
 };
 
 struct sway_server server;
diff --git a/include/sway/tree/arrange.h b/include/sway/tree/arrange.h
index 58235642..d6abcc81 100644
--- a/include/sway/tree/arrange.h
+++ b/include/sway/tree/arrange.h
@@ -11,26 +11,8 @@ void remove_gaps(struct sway_container *c);
 void add_gaps(struct sway_container *c);
 
 /**
- * Arrange layout for all the children of the given container, and add them to
- * the given transaction.
- *
- * Use this function if you need to arrange multiple sections of the tree in one
- * transaction.
- *
- * You must set the desired state of the container before calling
- * arrange_windows, then don't change any state-tracked properties in the
- * container until you've called transaction_commit.
+ * Arrange layout for all the children of the given container.
  */
-void arrange_windows(struct sway_container *container,
-		struct sway_transaction *transaction);
-
-/**
- * Arrange layout for the given container and commit the transaction.
- *
- * This function is a wrapper around arrange_windows, and handles creating and
- * committing the transaction for you. Use this function if you're only doing
- * one arrange operation.
- */
-void arrange_and_commit(struct sway_container *container);
+void arrange_windows(struct sway_container *container);
 
 #endif
diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h
index a69da9db..11780916 100644
--- a/include/sway/tree/container.h
+++ b/include/sway/tree/container.h
@@ -144,6 +144,10 @@ struct sway_container {
 
 	bool destroying;
 
+	// If true, indicates that the container has pending state that differs from
+	// the current.
+	bool dirty;
+
 	struct {
 		struct wl_signal destroy;
 		// Raised after the tree updates, but before arrange_windows
@@ -303,4 +307,10 @@ void container_get_box(struct sway_container *container, struct wlr_box *box);
 void container_floating_move_to(struct sway_container *con,
 		double lx, double ly);
 
+/**
+ * Mark a container as dirty if it isn't already. Dirty containers will be
+ * included in the next transaction then unmarked as dirty.
+ */
+void container_set_dirty(struct sway_container *container);
+
 #endif
diff --git a/sway/commands.c b/sway/commands.c
index addd64a6..50d949d4 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -9,6 +9,7 @@
 #include "sway/commands.h"
 #include "sway/config.h"
 #include "sway/criteria.h"
+#include "sway/desktop/transaction.h"
 #include "sway/security.h"
 #include "sway/input/input-manager.h"
 #include "sway/input/seat.h"
@@ -322,6 +323,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {
 cleanup:
 	free(exec);
 	free(views);
+	transaction_commit_dirty();
 	if (!results) {
 		results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
 	}
diff --git a/sway/commands/border.c b/sway/commands/border.c
index 6db85395..9c19e20a 100644
--- a/sway/commands/border.c
+++ b/sway/commands/border.c
@@ -42,7 +42,7 @@ struct cmd_results *cmd_border(int argc, char **argv) {
 		container_set_geometry_from_floating_view(view->swayc);
 	}
 
-	arrange_and_commit(view->swayc);
+	arrange_windows(view->swayc);
 
 	struct sway_seat *seat = input_manager_current_seat(input_manager);
 	if (seat->cursor) {
diff --git a/sway/commands/floating.c b/sway/commands/floating.c
index e6003521..6ab56c3b 100644
--- a/sway/commands/floating.c
+++ b/sway/commands/floating.c
@@ -37,7 +37,7 @@ struct cmd_results *cmd_floating(int argc, char **argv) {
 	container_set_floating(container, wants_floating);
 
 	struct sway_container *workspace = container_parent(container, C_WORKSPACE);
-	arrange_and_commit(workspace);
+	arrange_windows(workspace);
 
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
diff --git a/sway/commands/fullscreen.c b/sway/commands/fullscreen.c
index 1a4d8b41..0b5beaa2 100644
--- a/sway/commands/fullscreen.c
+++ b/sway/commands/fullscreen.c
@@ -34,7 +34,7 @@ struct cmd_results *cmd_fullscreen(int argc, char **argv) {
 	view_set_fullscreen(view, wants_fullscreen);
 
 	struct sway_container *workspace = container_parent(container, C_WORKSPACE);
-	arrange_and_commit(workspace->parent);
+	arrange_windows(workspace->parent);
 
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
diff --git a/sway/commands/gaps.c b/sway/commands/gaps.c
index 801fb179..3906eb70 100644
--- a/sway/commands/gaps.c
+++ b/sway/commands/gaps.c
@@ -43,7 +43,7 @@ struct cmd_results *cmd_gaps(int argc, char **argv) {
 			return cmd_results_new(CMD_INVALID, "gaps",
 				"gaps edge_gaps on|off|toggle");
 		}
-		arrange_and_commit(&root_container);
+		arrange_windows(&root_container);
 	} else {
 		int amount_idx = 0; // the current index in argv
 		enum gaps_op op = GAPS_OP_SET;
@@ -124,7 +124,7 @@ struct cmd_results *cmd_gaps(int argc, char **argv) {
 		if (amount_idx == 0) { // gaps <amount>
 			config->gaps_inner = val;
 			config->gaps_outer = val;
-			arrange_and_commit(&root_container);
+			arrange_windows(&root_container);
 			return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 		}
 		// Other variants. The middle-length variant (gaps inner|outer <amount>)
@@ -155,7 +155,7 @@ struct cmd_results *cmd_gaps(int argc, char **argv) {
 			} else {
 				config->gaps_outer = total;
 			}
-			arrange_and_commit(&root_container);
+			arrange_windows(&root_container);
 		} else {
 			struct sway_container *c =
 				config->handler_context.current_container;
@@ -169,7 +169,7 @@ struct cmd_results *cmd_gaps(int argc, char **argv) {
 				c->gaps_outer = total;
 			}
 
-			arrange_and_commit(c->parent ? c->parent : &root_container);
+			arrange_windows(c->parent ? c->parent : &root_container);
 		}
 	}
 
diff --git a/sway/commands/layout.c b/sway/commands/layout.c
index 9945fa5c..c446f1f9 100644
--- a/sway/commands/layout.c
+++ b/sway/commands/layout.c
@@ -49,7 +49,7 @@ struct cmd_results *cmd_layout(int argc, char **argv) {
 	}
 
 	container_notify_subtree_changed(parent);
-	arrange_and_commit(parent);
+	arrange_windows(parent);
 
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
diff --git a/sway/commands/move.c b/sway/commands/move.c
index a1c1e018..6ec050a8 100644
--- a/sway/commands/move.c
+++ b/sway/commands/move.c
@@ -6,7 +6,6 @@
 #include <wlr/types/wlr_output_layout.h>
 #include <wlr/util/log.h>
 #include "sway/commands.h"
-#include "sway/desktop/transaction.h"
 #include "sway/input/cursor.h"
 #include "sway/input/seat.h"
 #include "sway/output.h"
@@ -105,10 +104,8 @@ static struct cmd_results *cmd_move_container(struct sway_container *current,
 		// TODO: Ideally we would arrange the surviving parent after reaping,
 		// but container_reap_empty does not return it, so we arrange the
 		// workspace instead.
-		struct sway_transaction *txn = transaction_create();
-		arrange_windows(old_ws, txn);
-		arrange_windows(destination->parent, txn);
-		transaction_commit(txn);
+		arrange_windows(old_ws);
+		arrange_windows(destination->parent);
 
 		return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 	} else if (strcasecmp(argv[1], "to") == 0
@@ -144,10 +141,8 @@ static struct cmd_results *cmd_move_container(struct sway_container *current,
 		// TODO: Ideally we would arrange the surviving parent after reaping,
 		// but container_reap_empty does not return it, so we arrange the
 		// workspace instead.
-		struct sway_transaction *txn = transaction_create();
-		arrange_windows(old_ws, txn);
-		arrange_windows(focus->parent, txn);
-		transaction_commit(txn);
+		arrange_windows(old_ws);
+		arrange_windows(focus->parent);
 
 		return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 	}
@@ -177,10 +172,8 @@ static struct cmd_results *cmd_move_workspace(struct sway_container *current,
 	}
 	container_move_to(current, destination);
 
-	struct sway_transaction *txn = transaction_create();
-	arrange_windows(source, txn);
-	arrange_windows(destination, txn);
-	transaction_commit(txn);
+	arrange_windows(source);
+	arrange_windows(destination);
 
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
@@ -238,12 +231,10 @@ static struct cmd_results *move_in_direction(struct sway_container *container,
 	container_move(container, direction, move_amt);
 	struct sway_container *new_ws = container_parent(container, C_WORKSPACE);
 
-	struct sway_transaction *txn = transaction_create();
-	arrange_windows(old_ws, txn);
+	arrange_windows(old_ws);
 	if (new_ws != old_ws) {
-		arrange_windows(new_ws, txn);
+		arrange_windows(new_ws);
 	}
-	transaction_commit(txn);
 
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
diff --git a/sway/commands/reload.c b/sway/commands/reload.c
index c6715f9c..cea6a94b 100644
--- a/sway/commands/reload.c
+++ b/sway/commands/reload.c
@@ -12,6 +12,6 @@ struct cmd_results *cmd_reload(int argc, char **argv) {
 	}
 
 	load_swaybars();
-	arrange_and_commit(&root_container);
+	arrange_windows(&root_container);
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
diff --git a/sway/commands/resize.c b/sway/commands/resize.c
index 2cf811d8..e657864c 100644
--- a/sway/commands/resize.c
+++ b/sway/commands/resize.c
@@ -268,7 +268,7 @@ static void resize_tiled(int amount, enum resize_axis axis) {
 		}
 	}
 
-	arrange_and_commit(parent->parent);
+	arrange_windows(parent->parent);
 }
 
 /**
@@ -338,7 +338,7 @@ static struct cmd_results *resize_adjust_floating(enum resize_axis axis,
 		view->height += grow_height;
 	}
 
-	arrange_and_commit(con);
+	arrange_windows(con);
 
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
@@ -410,7 +410,7 @@ static struct cmd_results *resize_set_floating(struct sway_container *con,
 		view->height += grow_height;
 	}
 
-	arrange_and_commit(con);
+	arrange_windows(con);
 
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
diff --git a/sway/commands/smart_gaps.c b/sway/commands/smart_gaps.c
index f687e78e..7d27e571 100644
--- a/sway/commands/smart_gaps.c
+++ b/sway/commands/smart_gaps.c
@@ -23,7 +23,7 @@ struct cmd_results *cmd_smart_gaps(int argc, char **argv) {
 			"Expected 'smart_gaps <on|off>' ");
 	}
 
-	arrange_and_commit(&root_container);
+	arrange_windows(&root_container);
 
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
diff --git a/sway/commands/split.c b/sway/commands/split.c
index c40f4d9f..313799da 100644
--- a/sway/commands/split.c
+++ b/sway/commands/split.c
@@ -16,7 +16,7 @@ static struct cmd_results *do_split(int layout) {
 	}
 	struct sway_container *parent = container_split(con, layout);
 	container_create_notify(parent);
-	arrange_and_commit(parent->parent);
+	arrange_windows(parent->parent);
 
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
diff --git a/sway/commands/swap.c b/sway/commands/swap.c
index e052058f..2fc88308 100644
--- a/sway/commands/swap.c
+++ b/sway/commands/swap.c
@@ -1,7 +1,6 @@
 #include <strings.h>
 #include <wlr/util/log.h>
 #include "sway/commands.h"
-#include "sway/desktop/transaction.h"
 #include "sway/tree/arrange.h"
 #include "sway/tree/layout.h"
 #include "sway/tree/view.h"
@@ -79,14 +78,10 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
 
 	container_swap(current, other);
 
-	struct sway_transaction *txn = transaction_create();
-	arrange_windows(current->parent, txn);
-
+	arrange_windows(current->parent);
 	if (other->parent != current->parent) {
-		arrange_windows(other->parent, txn);
+		arrange_windows(other->parent);
 	}
 
-	transaction_commit(txn);
-
 	return cmd_results_new(CMD_SUCCESS, NULL, NULL);
 }
diff --git a/sway/config.c b/sway/config.c
index d2386f46..636f5f57 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -773,6 +773,6 @@ void config_update_font_height(bool recalculate) {
 	}
 
 	if (config->font_height != prev_max_height) {
-		arrange_and_commit(&root_container);
+		arrange_windows(&root_container);
 	}
 }
diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c
index 16910c7e..91baa6f8 100644
--- a/sway/desktop/layer_shell.c
+++ b/sway/desktop/layer_shell.c
@@ -12,7 +12,6 @@
 #include "sway/layers.h"
 #include "sway/output.h"
 #include "sway/server.h"
-#include "sway/tree/arrange.h"
 #include "sway/tree/layout.h"
 #include "log.h"
 
@@ -176,7 +175,7 @@ void arrange_layers(struct sway_output *output) {
 				sizeof(struct wlr_box)) != 0) {
 		wlr_log(WLR_DEBUG, "Usable area changed, rearranging output");
 		memcpy(&output->usable_area, &usable_area, sizeof(struct wlr_box));
-		arrange_and_commit(output->swayc);
+		container_set_dirty(output->swayc);
 	}
 
 	// Arrange non-exlusive surfaces from top->bottom
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index a2720885..a9808406 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -492,19 +492,21 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
 	output->wlr_output->data = NULL;
 	free(output);
 
-	arrange_and_commit(&root_container);
+	arrange_windows(&root_container);
 }
 
 static void handle_mode(struct wl_listener *listener, void *data) {
 	struct sway_output *output = wl_container_of(listener, output, mode);
 	arrange_layers(output);
-	arrange_and_commit(output->swayc);
+	arrange_windows(output->swayc);
+	transaction_commit_dirty();
 }
 
 static void handle_transform(struct wl_listener *listener, void *data) {
 	struct sway_output *output = wl_container_of(listener, output, transform);
 	arrange_layers(output);
-	arrange_and_commit(output->swayc);
+	arrange_windows(output->swayc);
+	transaction_commit_dirty();
 }
 
 static void handle_scale_iterator(struct sway_container *view, void *data) {
@@ -515,7 +517,8 @@ static void handle_scale(struct wl_listener *listener, void *data) {
 	struct sway_output *output = wl_container_of(listener, output, scale);
 	arrange_layers(output);
 	container_descendants(output->swayc, C_VIEW, handle_scale_iterator, NULL);
-	arrange_and_commit(output->swayc);
+	arrange_windows(output->swayc);
+	transaction_commit_dirty();
 }
 
 struct sway_output *output_from_wlr_output(struct wlr_output *wlr_output) {
@@ -584,5 +587,6 @@ void output_enable(struct sway_output *output) {
 	output->damage_destroy.notify = damage_handle_destroy;
 
 	arrange_layers(output);
-	arrange_and_commit(&root_container);
+	arrange_windows(&root_container);
+	transaction_commit_dirty();
 }
diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c
index 2b3f87c3..d7ef7130 100644
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -47,7 +47,7 @@ struct sway_transaction_instruction {
 	bool ready;
 };
 
-struct sway_transaction *transaction_create() {
+static struct sway_transaction *transaction_create() {
 	struct sway_transaction *transaction =
 		calloc(1, sizeof(struct sway_transaction));
 	transaction->instructions = create_list();
@@ -141,23 +141,8 @@ static void copy_pending_state(struct sway_container *container,
 	}
 }
 
-static bool transaction_has_container(struct sway_transaction *transaction,
+static void transaction_add_container(struct sway_transaction *transaction,
 		struct sway_container *container) {
-	for (int i = 0; i < transaction->instructions->length; ++i) {
-		struct sway_transaction_instruction *instruction =
-			transaction->instructions->items[i];
-		if (instruction->container == container) {
-			return true;
-		}
-	}
-	return false;
-}
-
-void transaction_add_container(struct sway_transaction *transaction,
-		struct sway_container *container) {
-	if (transaction_has_container(transaction, container)) {
-		return;
-	}
 	struct sway_transaction_instruction *instruction =
 		calloc(1, sizeof(struct sway_transaction_instruction));
 	instruction->transaction = transaction;
@@ -285,7 +270,7 @@ static bool should_configure(struct sway_container *con,
 	return true;
 }
 
-void transaction_commit(struct sway_transaction *transaction) {
+static void transaction_commit(struct sway_transaction *transaction) {
 	wlr_log(WLR_DEBUG, "Transaction %p committing with %i instructions",
 			transaction, transaction->instructions->length);
 	transaction->num_waiting = 0;
@@ -418,3 +403,17 @@ struct wlr_texture *transaction_get_saved_texture(struct sway_view *view,
 	*height = instruction->saved_buffer_height;
 	return instruction->saved_buffer->texture;
 }
+
+void transaction_commit_dirty() {
+	if (!server.dirty_containers->length) {
+		return;
+	}
+	struct sway_transaction *transaction = transaction_create();
+	for (int i = 0; i < server.dirty_containers->length; ++i) {
+		struct sway_container *container = server.dirty_containers->items[i];
+		transaction_add_container(transaction, container);
+		container->dirty = false;
+	}
+	server.dirty_containers->length = 0;
+	transaction_commit(transaction);
+}
diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c
index fbeeb2e3..98c16faf 100644
--- a/sway/desktop/xdg_shell.c
+++ b/sway/desktop/xdg_shell.c
@@ -244,7 +244,8 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
 	view_set_fullscreen(view, e->fullscreen);
 
 	struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
-	arrange_and_commit(output);
+	arrange_windows(output);
+	transaction_commit_dirty();
 }
 
 static void handle_unmap(struct wl_listener *listener, void *data) {
@@ -281,10 +282,11 @@ static void handle_map(struct wl_listener *listener, void *data) {
 	if (xdg_surface->toplevel->client_pending.fullscreen) {
 		view_set_fullscreen(view, true);
 		struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
-		arrange_and_commit(ws);
+		arrange_windows(ws);
 	} else {
-		arrange_and_commit(view->swayc->parent);
+		arrange_windows(view->swayc->parent);
 	}
+	transaction_commit_dirty();
 
 	xdg_shell_view->commit.notify = handle_commit;
 	wl_signal_add(&xdg_surface->surface->events.commit,
diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c
index 88d9bb94..4d76f0a7 100644
--- a/sway/desktop/xdg_shell_v6.c
+++ b/sway/desktop/xdg_shell_v6.c
@@ -239,7 +239,8 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
 	view_set_fullscreen(view, e->fullscreen);
 
 	struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
-	arrange_and_commit(output);
+	arrange_windows(output);
+	transaction_commit_dirty();
 }
 
 static void handle_unmap(struct wl_listener *listener, void *data) {
@@ -276,10 +277,11 @@ static void handle_map(struct wl_listener *listener, void *data) {
 	if (xdg_surface->toplevel->client_pending.fullscreen) {
 		view_set_fullscreen(view, true);
 		struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
-		arrange_and_commit(ws);
+		arrange_windows(ws);
 	} else {
-		arrange_and_commit(view->swayc->parent);
+		arrange_windows(view->swayc->parent);
 	}
+	transaction_commit_dirty();
 
 	xdg_shell_v6_view->commit.notify = handle_commit;
 	wl_signal_add(&xdg_surface->surface->events.commit,
diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c
index 460d1cc8..11516673 100644
--- a/sway/desktop/xwayland.c
+++ b/sway/desktop/xwayland.c
@@ -333,10 +333,11 @@ static void handle_map(struct wl_listener *listener, void *data) {
 	if (xsurface->fullscreen) {
 		view_set_fullscreen(view, true);
 		struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
-		arrange_and_commit(ws);
+		arrange_windows(ws);
 	} else {
-		arrange_and_commit(view->swayc->parent);
+		arrange_windows(view->swayc->parent);
 	}
+	transaction_commit_dirty();
 }
 
 static void handle_destroy(struct wl_listener *listener, void *data) {
@@ -392,7 +393,8 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
 	view_set_fullscreen(view, xsurface->fullscreen);
 
 	struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
-	arrange_and_commit(output);
+	arrange_windows(output);
+	transaction_commit_dirty();
 }
 
 static void handle_set_title(struct wl_listener *listener, void *data) {
diff --git a/sway/server.c b/sway/server.c
index 1d8eb964..8566d512 100644
--- a/sway/server.c
+++ b/sway/server.c
@@ -125,8 +125,7 @@ bool server_init(struct sway_server *server) {
 	if (debug != NULL && strcmp(debug, "txn_timings") == 0) {
 		server->debug_txn_timings = true;
 	}
-	server->destroying_containers = create_list();
-
+	server->dirty_containers = create_list();
 	server->transactions = create_list();
 
 	input_manager = input_manager_create(server);
@@ -136,7 +135,7 @@ bool server_init(struct sway_server *server) {
 void server_fini(struct sway_server *server) {
 	// TODO: free sway-specific resources
 	wl_display_destroy(server->wl_display);
-	list_free(server->destroying_containers);
+	list_free(server->dirty_containers);
 	list_free(server->transactions);
 }
 
diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c
index bcc3ee9a..533cf71c 100644
--- a/sway/tree/arrange.c
+++ b/sway/tree/arrange.c
@@ -144,38 +144,22 @@ static void apply_tabbed_or_stacked_layout(struct sway_container *parent) {
 	}
 }
 
-/**
- * If a container has been deleted from the pending tree state, we must add it
- * to the transaction so it can be freed afterwards. To do this, we iterate the
- * server's destroying_containers list and add all of them. We may add more than
- * what we need to, but this is easy and has no negative consequences.
- */
-static void add_deleted_containers(struct sway_transaction *transaction) {
-	for (int i = 0; i < server.destroying_containers->length; ++i) {
-		struct sway_container *child = server.destroying_containers->items[i];
-		transaction_add_container(transaction, child);
-	}
-}
+static void arrange_children_of(struct sway_container *parent);
 
-static void arrange_children_of(struct sway_container *parent,
-		struct sway_transaction *transaction);
-
-static void arrange_floating(struct sway_container *floating,
-		struct sway_transaction *transaction) {
+static void arrange_floating(struct sway_container *floating) {
 	for (int i = 0; i < floating->children->length; ++i) {
 		struct sway_container *floater = floating->children->items[i];
 		if (floater->type == C_VIEW) {
 			view_autoconfigure(floater->sway_view);
 		} else {
-			arrange_children_of(floater, transaction);
+			arrange_children_of(floater);
 		}
-		transaction_add_container(transaction, floater);
+		container_set_dirty(floater);
 	}
-	transaction_add_container(transaction, floating);
+	container_set_dirty(floating);
 }
 
-static void arrange_children_of(struct sway_container *parent,
-		struct sway_transaction *transaction) {
+static void arrange_children_of(struct sway_container *parent) {
 	if (config->reloading) {
 		return;
 	}
@@ -198,7 +182,7 @@ static void arrange_children_of(struct sway_container *parent,
 		apply_horiz_layout(parent);
 		break;
 	case L_FLOATING:
-		arrange_floating(parent, transaction);
+		arrange_floating(parent);
 		break;
 	}
 
@@ -213,14 +197,13 @@ static void arrange_children_of(struct sway_container *parent,
 		if (child->type == C_VIEW) {
 			view_autoconfigure(child->sway_view);
 		} else {
-			arrange_children_of(child, transaction);
+			arrange_children_of(child);
 		}
-		transaction_add_container(transaction, child);
+		container_set_dirty(child);
 	}
 }
 
-static void arrange_workspace(struct sway_container *workspace,
-		struct sway_transaction *transaction) {
+static void arrange_workspace(struct sway_container *workspace) {
 	if (config->reloading) {
 		return;
 	}
@@ -234,15 +217,14 @@ static void arrange_workspace(struct sway_container *workspace,
 	workspace->x = output->x + area->x;
 	workspace->y = output->y + area->y;
 	add_gaps(workspace);
-	transaction_add_container(transaction, workspace);
+	container_set_dirty(workspace);
 	wlr_log(WLR_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name,
 			workspace->x, workspace->y);
-	arrange_floating(workspace->sway_workspace->floating, transaction);
-	arrange_children_of(workspace, transaction);
+	arrange_floating(workspace->sway_workspace->floating);
+	arrange_children_of(workspace);
 }
 
-static void arrange_output(struct sway_container *output,
-		struct sway_transaction *transaction) {
+static void arrange_output(struct sway_container *output) {
 	if (config->reloading) {
 		return;
 	}
@@ -253,16 +235,16 @@ static void arrange_output(struct sway_container *output,
 	output->y = output_box->y;
 	output->width = output_box->width;
 	output->height = output_box->height;
-	transaction_add_container(transaction, output);
+	container_set_dirty(output);
 	wlr_log(WLR_DEBUG, "Arranging output '%s' at %f,%f",
 			output->name, output->x, output->y);
 	for (int i = 0; i < output->children->length; ++i) {
 		struct sway_container *workspace = output->children->items[i];
-		arrange_workspace(workspace, transaction);
+		arrange_workspace(workspace);
 	}
 }
 
-static void arrange_root(struct sway_transaction *transaction) {
+static void arrange_root() {
 	if (config->reloading) {
 		return;
 	}
@@ -274,43 +256,35 @@ static void arrange_root(struct sway_transaction *transaction) {
 	root_container.y = layout_box->y;
 	root_container.width = layout_box->width;
 	root_container.height = layout_box->height;
-	transaction_add_container(transaction, &root_container);
+	container_set_dirty(&root_container);
 	for (int i = 0; i < root_container.children->length; ++i) {
 		struct sway_container *output = root_container.children->items[i];
-		arrange_output(output, transaction);
+		arrange_output(output);
 	}
 }
 
-void arrange_windows(struct sway_container *container,
-		struct sway_transaction *transaction) {
+void arrange_windows(struct sway_container *container) {
 	switch (container->type) {
 	case C_ROOT:
-		arrange_root(transaction);
+		arrange_root();
 		break;
 	case C_OUTPUT:
-		arrange_output(container, transaction);
+		arrange_output(container);
 		break;
 	case C_WORKSPACE:
-		arrange_workspace(container, transaction);
+		arrange_workspace(container);
 		break;
 	case C_CONTAINER:
-		arrange_children_of(container, transaction);
-		transaction_add_container(transaction, container);
+		arrange_children_of(container);
+		container_set_dirty(container);
 		break;
 	case C_VIEW:
 		view_autoconfigure(container->sway_view);
-		transaction_add_container(transaction, container);
+		container_set_dirty(container);
 		break;
 	case C_TYPES:
 		break;
 	}
-	add_deleted_containers(transaction);
-}
-
-void arrange_and_commit(struct sway_container *container) {
-	struct sway_transaction *transaction = transaction_create();
-	arrange_windows(container, transaction);
-	transaction_commit(transaction);
 }
 
 void remove_gaps(struct sway_container *c) {
diff --git a/sway/tree/container.c b/sway/tree/container.c
index 58852717..35f67cce 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -159,14 +159,6 @@ void container_free(struct sway_container *cont) {
 	wlr_texture_destroy(cont->title_focused_inactive);
 	wlr_texture_destroy(cont->title_unfocused);
 	wlr_texture_destroy(cont->title_urgent);
-
-	for (int i = 0; i < server.destroying_containers->length; ++i) {
-		if (server.destroying_containers->items[i] == cont) {
-			list_del(server.destroying_containers, i);
-			break;
-		}
-	}
-
 	list_free(cont->instructions);
 	list_free(cont->children);
 	list_free(cont->current.children);
@@ -325,7 +317,7 @@ static struct sway_container *container_destroy_noreaping(
 	}
 
 	con->destroying = true;
-	list_add(server.destroying_containers, con);
+	container_set_dirty(con);
 
 	if (!con->parent) {
 		return NULL;
@@ -1069,9 +1061,15 @@ void container_floating_move_to(struct sway_container *con,
 	if (old_workspace != new_workspace) {
 		container_remove_child(con);
 		container_add_child(new_workspace->sway_workspace->floating, con);
-		struct sway_transaction *transaction = transaction_create();
-		arrange_windows(old_workspace, transaction);
-		arrange_windows(new_workspace, transaction);
-		transaction_commit(transaction);
+		arrange_windows(old_workspace);
+		arrange_windows(new_workspace);
 	}
 }
+
+void container_set_dirty(struct sway_container *container) {
+	if (container->dirty) {
+		return;
+	}
+	container->dirty = true;
+	list_add(server.dirty_containers, container);
+}
diff --git a/sway/tree/layout.c b/sway/tree/layout.c
index ba234e89..54ddb3f9 100644
--- a/sway/tree/layout.c
+++ b/sway/tree/layout.c
@@ -22,7 +22,8 @@ struct sway_container root_container;
 
 static void output_layout_handle_change(struct wl_listener *listener,
 		void *data) {
-	arrange_and_commit(&root_container);
+	arrange_windows(&root_container);
+	transaction_commit_dirty();
 }
 
 void layout_init(void) {
diff --git a/sway/tree/view.c b/sway/tree/view.c
index b356183c..bf380d98 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -594,11 +594,12 @@ void view_unmap(struct sway_view *view) {
 		ws->sway_workspace->fullscreen = NULL;
 		container_destroy(view->swayc);
 
-		arrange_and_commit(ws->parent);
+		arrange_windows(ws->parent);
 	} else {
 		struct sway_container *parent = container_destroy(view->swayc);
-		arrange_and_commit(parent);
+		arrange_windows(parent);
 	}
+	transaction_commit_dirty();
 	view->surface = NULL;
 }
 
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
index 50f9400a..2a2d834a 100644
--- a/sway/tree/workspace.c
+++ b/sway/tree/workspace.c
@@ -427,7 +427,7 @@ bool workspace_switch(struct sway_container *workspace) {
 	}
 	seat_set_focus(seat, next);
 	struct sway_container *output = container_parent(workspace, C_OUTPUT);
-	arrange_and_commit(output);
+	arrange_windows(output);
 	return true;
 }