diff --git a/.changeset/flat-jars-search.md b/.changeset/flat-jars-search.md
new file mode 100644
index 0000000000..fc0de76f95
--- /dev/null
+++ b/.changeset/flat-jars-search.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: memoize `clsx` calls
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
index 6122dc4e0e..9b3ecc922d 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js
@@ -511,22 +511,21 @@ function setup_select_synchronization(value_binding, context) {
 /**
  * @param {AST.ClassDirective[]} class_directives
  * @param {ComponentContext} context
- * @return {ObjectExpression}
+ * @return {ObjectExpression | Identifier}
  */
 export function build_class_directives_object(class_directives, context) {
 	let properties = [];
+	let has_call_or_state = false;
 
 	for (const d of class_directives) {
-		let expression = /** @type Expression */ (context.visit(d.expression));
-
-		if (d.metadata.expression.has_call) {
-			expression = get_expression_id(context.state, expression);
-		}
-
+		const expression = /** @type Expression */ (context.visit(d.expression));
 		properties.push(b.init(d.name, expression));
+		has_call_or_state ||= d.metadata.expression.has_call || d.metadata.expression.has_state;
 	}
 
-	return b.object(properties);
+	const directives = b.object(properties);
+
+	return has_call_or_state ? get_expression_id(context.state, directives) : directives;
 }
 
 /**
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
index e0eb04d823..084c1e7c67 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js
@@ -162,13 +162,13 @@ export function get_attribute_name(element, attribute) {
  * @param {boolean} is_html
  */
 export function build_set_class(element, node_id, attribute, class_directives, context, is_html) {
-	let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
-		metadata.has_call ? get_expression_id(context.state, value) : value
-	);
+	let { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => {
+		if (attribute.metadata.needs_clsx) {
+			value = b.call('$.clsx', value);
+		}
 
-	if (attribute && attribute.metadata.needs_clsx) {
-		value = b.call('$.clsx', value);
-	}
+		return metadata.has_call ? get_expression_id(context.state, value) : value;
+	});
 
 	/** @type {Identifier | undefined} */
 	let previous_id;
@@ -176,7 +176,7 @@ export function build_set_class(element, node_id, attribute, class_directives, c
 	/** @type {ObjectExpression | Identifier | undefined} */
 	let prev;
 
-	/** @type {ObjectExpression | undefined} */
+	/** @type {ObjectExpression | Identifier | undefined} */
 	let next;
 
 	if (class_directives.length) {
diff --git a/packages/svelte/src/internal/client/dom/elements/class.js b/packages/svelte/src/internal/client/dom/elements/class.js
index 7027c84f62..ecbfcbc010 100644
--- a/packages/svelte/src/internal/client/dom/elements/class.js
+++ b/packages/svelte/src/internal/client/dom/elements/class.js
@@ -33,7 +33,7 @@ export function set_class(dom, is_html, value, hash, prev_classes, next_classes)
 
 		// @ts-expect-error need to add __className to patched prototype
 		dom.__className = value;
-	} else if (next_classes) {
+	} else if (next_classes && prev_classes !== next_classes) {
 		for (var key in next_classes) {
 			var is_present = !!next_classes[key];