diff --git a/.changeset/lucky-pears-smoke.md b/.changeset/lucky-pears-smoke.md
new file mode 100644
index 0000000000..816a37dca5
--- /dev/null
+++ b/.changeset/lucky-pears-smoke.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: give effect teardowns the value from before the first write in a flush
diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js
index 5becae2dc9..8251f79298 100644
--- a/packages/svelte/src/internal/client/reactivity/batch.js
+++ b/packages/svelte/src/internal/client/reactivity/batch.js
@@ -420,6 +420,7 @@ export class Batch {
}
if (next_batch !== null) {
+ old_values.clear();
next_batch.#process();
}
}
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index 218941ab67..70be6489a4 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -180,7 +180,13 @@ export function set(source, value, should_proxy = false) {
*/
export function internal_set(source, value, updated_during_traversal = null) {
if (!source.equals(value)) {
- old_values.set(source, is_destroying_effect ? value : source.v);
+ if (is_destroying_effect) {
+ old_values.set(source, value);
+ } else if (!old_values.has(source)) {
+ // only record the value from before the first write in this flush, otherwise a
+ // teardown would see the value from before whichever write happened to be last
+ old_values.set(source, source.v);
+ }
var batch = Batch.ensure();
batch.capture(source, value);
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/_config.js
new file mode 100644
index 0000000000..7db4966106
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/_config.js
@@ -0,0 +1,17 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target, logs }) {
+ const button = target.querySelector('button');
+
+ flushSync(() => button?.click());
+
+ assert.deepEqual(logs, [
+ 'register: one',
+ 'unregister: one',
+ 'leftover: none',
+ 'register: three'
+ ]);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/main.svelte
new file mode 100644
index 0000000000..a3932aaa22
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/main.svelte
@@ -0,0 +1,21 @@
+
+
+