Introduction

The $derived rune creates values that are computed from other reactive values. When those source values change, the derived value automatically recalculates. This is one of the most powerful concepts in Svelte 5’s reactivity system—it lets you express complex data relationships declaratively while the framework handles all the update logic.

Analogy: Think of a derived value like a live scoreboard at a sports game. The scoreboard doesn’t have its own opinion about the score—it simply reflects the current state of the game. When a team scores, the scoreboard automatically updates.

Svelte 5 provides two forms of the derived rune:

  • $derived(expression) — For simple, single-expression computations
  • $derived.by(() => { ... }) — For complex logic requiring multiple statements

This tutorial covers each form in depth, explains when to use which, and provides extensive real-world examples to help you master reactive computations in Svelte 5.


Part 1: The $derived Rune

What Is $derived?

$derived creates a reactive value from a single expression. When any reactive value in that expression changes, the derived value automatically recalculates.

Key Constraint

Derived values are automatically memoized—they only recalculate when dependencies change, making them perfect for expensive computations.

<script>
	let count = $state(5)
	let doubled = $derived(count * 2) // Always count * 2
</script>

<p>{count} × 2 = {doubled}</p>

The key constraint: $derived() takes exactly one expression — no semicolons, no multiple statements, no control flow. Think of it as a formula in a spreadsheet cell.

Basic Syntax

let derivedValue = $derived(expression)

Where expression can be:

  • Arithmetic: count * 2, price + tax
  • Method calls: items.filter(...), name.toUpperCase()
  • Ternary operators: isActive ? 'Yes' : 'No'
  • Template literals: `Hello, ${name}!`
  • Property access: user.profile.name
  • Chained methods: items.filter(...).map(...).join(', ')

When to Use $derived

Use $derived when your computation fits in a single expression with no intermediate steps.

Good use cases for $derived:

<script>
	let price = $state(100)
	let quantity = $state(2)
	let taxRate = $state(0.08)
	let items = $state(['banana', 'apple', 'cherry'])
	let user = $state({ firstName: 'Alice', lastName: 'Smith' })

	// Simple arithmetic
	let subtotal = $derived(price * quantity)
	let tax = $derived(subtotal * taxRate)
	let total = $derived(subtotal + tax)

	// Array operations
	let sorted = $derived([...items].sort())
	let count = $derived(items.length)
	let first = $derived(items[0])
	let hasItems = $derived(items.length > 0)

	// String operations
	let fullName = $derived(`${user.firstName} ${user.lastName}`)
	let initials = $derived(`${user.firstName[0]}${user.lastName[0]}`)

	// Ternary expressions
	let status = $derived(total > 500 ? 'Premium Order' : 'Standard Order')
	let discount = $derived(quantity >= 10 ? 0.1 : 0)

	// Chained methods (still one expression)
	let processed = $derived(
		items
			.filter((i) => i.length > 4)
			.map((i) => i.toUpperCase())
			.join(', ')
	)

	// Nullish coalescing and optional chaining
	let displayName = $derived(user?.nickname ?? user.firstName)
</script>

Rule of thumb: If you can write it without semicolons or curly braces { }, use $derived().

Real-World Examples with $derived

1. Shopping Cart Totals

<script>
	let cart = $state([
		{ name: 'T-Shirt', price: 25.99, quantity: 2 },
		{ name: 'Jeans', price: 59.99, quantity: 1 },
		{ name: 'Socks', price: 8.99, quantity: 3 }
	])

	let itemCount = $derived(cart.reduce((sum, item) => sum + item.quantity, 0))
	let subtotal = $derived(cart.reduce((sum, item) => sum + item.price * item.quantity, 0))
	let shipping = $derived(subtotal > 100 ? 0 : 9.99)
	let total = $derived(subtotal + shipping)
	let freeShippingRemaining = $derived(Math.max(0, 100 - subtotal))
</script>

<h2>Your Cart ({itemCount} items)</h2>

{#each cart as item}
	<div class="cart-item">
		<span>{item.name}</span>
		<input type="number" bind:value={item.quantity} min="1" />
		<span>${(item.price * item.quantity).toFixed(2)}</span>
	</div>
{/each}

<hr />
<p>Subtotal: ${subtotal.toFixed(2)}</p>
<p>Shipping: {shipping === 0 ? 'FREE' : `$${shipping.toFixed(2)}`}</p>

{#if freeShippingRemaining > 0}
	<p class="promo">Add ${freeShippingRemaining.toFixed(2)} more for free shipping!</p>
{/if}

<p class="total">Total: ${total.toFixed(2)}</p>

2. Search and Filter

<script>
	let products = $state([
		{ name: 'MacBook Pro', category: 'Electronics', price: 1999 },
		{ name: 'iPhone', category: 'Electronics', price: 999 },
		{ name: 'Coffee Maker', category: 'Kitchen', price: 79 },
		{ name: 'Standing Desk', category: 'Furniture', price: 599 },
		{ name: 'Headphones', category: 'Electronics', price: 299 }
	])

	let searchQuery = $state('')
	let selectedCategory = $state('all')
	let maxPrice = $state(2000)

	// Each filter step is a simple expression
	let searchFiltered = $derived(
		products.filter((p) => p.name.toLowerCase().includes(searchQuery.toLowerCase()))
	)

	let categoryFiltered = $derived(
		selectedCategory === 'all'
			? searchFiltered
			: searchFiltered.filter((p) => p.category === selectedCategory)
	)

	let priceFiltered = $derived(categoryFiltered.filter((p) => p.price <= maxPrice))

	let resultCount = $derived(priceFiltered.length)
	let categories = $derived([...new Set(products.map((p) => p.category))])
</script>

<input type="search" bind:value={searchQuery} placeholder="Search products..." />

<select bind:value={selectedCategory}>
	<option value="all">All Categories</option>
	{#each categories as cat}
		<option value={cat}>{cat}</option>
	{/each}
</select>

<label>
	Max Price: ${maxPrice}
	<input type="range" bind:value={maxPrice} min="0" max="2000" />
</label>

<p>Showing {resultCount} products</p>

{#each priceFiltered as product}
	<div class="product">
		<h3>{product.name}</h3>
		<p>{product.category} • ${product.price}</p>
	</div>
{/each}

3. Real-Time Character Counter

<script>
	let text = $state('')
	let maxLength = $state(280)

	let charCount = $derived(text.length)
	let remaining = $derived(maxLength - charCount)
	let percentage = $derived((charCount / maxLength) * 100)
	let isOverLimit = $derived(charCount > maxLength)
	let isNearLimit = $derived(remaining <= 20 && remaining > 0)
	let wordCount = $derived(text.trim() ? text.trim().split(/\s+/).length : 0)
</script>

<textarea bind:value={text} placeholder="What's happening?"></textarea>

<div class="counter" class:warning={isNearLimit} class:error={isOverLimit}>
	{remaining} characters remaining • {wordCount} words
</div>

<div class="progress-bar">
	<div
		class="progress"
		style="width: {Math.min(percentage, 100)}%"
		class:warning={isNearLimit}
		class:error={isOverLimit}
	></div>
</div>

<button disabled={isOverLimit || charCount === 0}> Post </button>

<style>
	.warning {
		color: #f39c12;
	}
	.error {
		color: #e74c3c;
	}
	.progress-bar {
		height: 4px;
		background: #eee;
		border-radius: 2px;
	}
	.progress {
		height: 100%;
		background: #3498db;
		transition: width 0.2s;
	}
	.progress.warning {
		background: #f39c12;
	}
	.progress.error {
		background: #e74c3c;
	}
</style>

4. Currency Converter

<script>
	let amount = $state(100)
	let fromCurrency = $state('USD')
	let toCurrency = $state('EUR')

	// In real app, these would come from an API
	let rates = $state({
		USD: 1,
		EUR: 0.92,
		GBP: 0.79,
		JPY: 149.5,
		CAD: 1.36
	})

	let converted = $derived((amount / rates[fromCurrency]) * rates[toCurrency])
	let formattedResult = $derived(
		new Intl.NumberFormat('en-US', {
			style: 'currency',
			currency: toCurrency
		}).format(converted)
	)
	let exchangeRate = $derived((rates[toCurrency] / rates[fromCurrency]).toFixed(4))
</script>

<input type="number" bind:value={amount} />
<select bind:value={fromCurrency}>
	{#each Object.keys(rates) as currency}
		<option value={currency}>{currency}</option>
	{/each}
</select>

<span></span>

<select bind:value={toCurrency}>
	{#each Object.keys(rates) as currency}
		<option value={currency}>{currency}</option>
	{/each}
</select>

<p class="result">{formattedResult}</p>
<p class="rate">1 {fromCurrency} = {exchangeRate} {toCurrency}</p>

5. Date and Time Formatting

<script>
	let selectedDate = $state(new Date())

	let formattedDate = $derived(selectedDate.toLocaleDateString('en-US', { dateStyle: 'full' }))
	let formattedTime = $derived(selectedDate.toLocaleTimeString('en-US', { timeStyle: 'short' }))
	let relativeTime = $derived(getRelativeTime(selectedDate))
	let dayOfYear = $derived(
		Math.floor((selectedDate - new Date(selectedDate.getFullYear(), 0, 0)) / (1000 * 60 * 60 * 24))
	)
	let weekNumber = $derived(Math.ceil(dayOfYear / 7))
	let isWeekend = $derived([0, 6].includes(selectedDate.getDay()))
	let quarter = $derived(Math.ceil((selectedDate.getMonth() + 1) / 3))

	function getRelativeTime(date) {
		const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
		const diff = date - new Date()
		const days = Math.round(diff / (1000 * 60 * 60 * 24))
		if (Math.abs(days) < 1) return 'today'
		return rtf.format(days, 'day')
	}
</script>

<input type="datetime-local" bind:value={selectedDate} />

<dl>
	<dt>Full Date</dt>
	<dd>{formattedDate}</dd>

	<dt>Time</dt>
	<dd>{formattedTime}</dd>

	<dt>Relative</dt>
	<dd>{relativeTime}</dd>

	<dt>Day of Year</dt>
	<dd>{dayOfYear}</dd>

	<dt>Week Number</dt>
	<dd>{weekNumber}</dd>

	<dt>Quarter</dt>
	<dd>Q{quarter}</dd>

	<dt>Weekend?</dt>
	<dd>{isWeekend ? 'Yes 🎉' : 'No'}</dd>
</dl>

Derived Values vs. Direct Template Calculations

Why use $derived instead of calculating directly in the template?

<script>
	let items = $state([
		{ name: 'Apple', price: 1.5, quantity: 3 },
		{ name: 'Banana', price: 0.75, quantity: 5 }
	])

	// With $derived - calculated once, reused everywhere
	let total = $derived(items.reduce((sum, item) => sum + item.price * item.quantity, 0))
</script>

<!-- Option 1: Using $derived (better) -->
<p>Total: ${total.toFixed(2)}</p>
<p>With tax: ${(total * 1.08).toFixed(2)}</p>
<p>Per item average: ${(total / items.length).toFixed(2)}</p>

<!-- Option 2: Calculating in template (worse) -->
<p>Total: ${items.reduce((s, i) => s + i.price * i.quantity, 0).toFixed(2)}</p>
<p>With tax: ${(items.reduce((s, i) => s + i.price * i.quantity, 0) * 1.08).toFixed(2)}</p>
<!-- The reduce runs THREE times! -->

Benefits of $derived:

  • Calculates once per change, caches the result
  • Makes code more readable
  • Can be reused across the template
  • Easier to debug and test

Part 2: The $derived.by Rune

What Is $derived.by?

$derived.by creates a reactive value from a function body. Unlike $derived, it can contain multiple statements, intermediate variables, control flow, loops, try/catch blocks, and early returns.

<script>
	let numbers = $state([1, 5, 3, 9, 2, 7])

	let stats = $derived.by(() => {
		// Multiple statements and variables
		const sorted = [...numbers].sort((a, b) => a - b)
		const sum = numbers.reduce((a, b) => a + b, 0)
		const avg = sum / numbers.length

		// Multi-step calculation
		const mid = Math.floor(sorted.length / 2)
		const median = sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid]

		// Return the computed result
		return { min: sorted[0], max: sorted.at(-1), sum, avg, median }
	})
</script>

Basic Syntax

let derivedValue = $derived.by(() => {
	// Multiple statements
	// Local variables
	// Control flow
	return result
})

The function passed to $derived.by must:

  • Be synchronous (no async/await)
  • Return a value
  • Be a pure computation (no side effects)

When to Use $derived.by

Use $derived.by when you need:

  1. Multiple statements — More than one line of logic
  2. Intermediate variables — Variables for storing partial results
  3. Control flowif/else, switch, for, while
  4. Early returns — Return from different code paths
  5. Try/catch — Error handling
  6. Complex transformations — Multi-step data processing

Real-World Examples with $derived.by

1. User Permissions System

<script>
	let user = $state({
		id: 1,
		name: 'Alice',
		role: 'editor',
		subscription: 'premium',
		verified: true,
		loginCount: 42,
		createdAt: new Date('2023-01-15')
	})

	let permissions = $derived.by(() => {
		// Admin gets everything - early return
		if (user.role === 'admin') {
			return {
				canRead: true,
				canWrite: true,
				canDelete: true,
				canPublish: true,
				canManageUsers: true,
				canAccessAnalytics: true,
				canExport: true
			}
		}

		// Start with base permissions
		const perms = {
			canRead: true,
			canWrite: false,
			canDelete: false,
			canPublish: false,
			canManageUsers: false,
			canAccessAnalytics: false,
			canExport: false
		}

		// Role-based permissions
		if (user.role === 'editor') {
			perms.canWrite = true
			perms.canPublish = true
		} else if (user.role === 'author') {
			perms.canWrite = true
		}

		// Subscription-based upgrades
		if (user.subscription === 'premium') {
			perms.canAccessAnalytics = true
			perms.canExport = true
		} else if (user.subscription === 'pro') {
			perms.canExport = true
		}

		// Verified users can delete their own content
		if (user.verified) {
			perms.canDelete = true
		}

		// Veteran users (1+ year) get analytics
		const accountAge = Date.now() - user.createdAt.getTime()
		const oneYear = 365 * 24 * 60 * 60 * 1000
		if (accountAge > oneYear) {
			perms.canAccessAnalytics = true
		}

		return perms
	})
</script>

<h2>Permissions for {user.name}</h2>
<ul>
	{#each Object.entries(permissions) as [perm, allowed]}
		<li class:granted={allowed} class:denied={!allowed}>
			{perm}: {allowed ? 'yes' : 'no'}
		</li>
	{/each}
</ul>

2. Form Validation with Detailed Errors

<script>
	let form = $state({
		email: '',
		password: '',
		confirmPassword: '',
		age: '',
		website: ''
	})

	let validation = $derived.by(() => {
		const errors = {}
		const warnings = {}

		// Email validation
		if (!form.email) {
			errors.email = 'Email is required'
		} else if (!form.email.includes('@')) {
			errors.email = 'Please enter a valid email address'
		} else if (form.email.endsWith('.con')) {
			warnings.email = 'Did you mean .com?'
		}

		// Password validation
		if (!form.password) {
			errors.password = 'Password is required'
		} else {
			const pwd = form.password
			const issues = []

			if (pwd.length < 8) issues.push('at least 8 characters')
			if (!/[A-Z]/.test(pwd)) issues.push('one uppercase letter')
			if (!/[a-z]/.test(pwd)) issues.push('one lowercase letter')
			if (!/[0-9]/.test(pwd)) issues.push('one number')
			if (!/[^A-Za-z0-9]/.test(pwd)) issues.push('one special character')

			if (issues.length > 0) {
				errors.password = `Password needs: ${issues.join(', ')}`
			}
		}

		// Confirm password
		if (form.password && form.confirmPassword !== form.password) {
			errors.confirmPassword = 'Passwords do not match'
		}

		// Age validation
		if (form.age) {
			const age = parseInt(form.age)
			if (isNaN(age)) {
				errors.age = 'Please enter a valid number'
			} else if (age < 13) {
				errors.age = 'You must be at least 13 years old'
			} else if (age > 120) {
				errors.age = 'Please enter a realistic age'
			} else if (age < 18) {
				warnings.age = 'Parental consent may be required'
			}
		}

		// Website validation (optional field)
		if (form.website) {
			try {
				new URL(form.website)
			} catch {
				// Try adding https://
				try {
					new URL(`https://${form.website}`)
					warnings.website = 'Consider adding https://'
				} catch {
					errors.website = 'Please enter a valid URL'
				}
			}
		}

		const errorCount = Object.keys(errors).length
		const warningCount = Object.keys(warnings).length
		const isValid = errorCount === 0

		return { errors, warnings, errorCount, warningCount, isValid }
	})
</script>

<form>
	<div class="field">
		<label>Email</label>
		<input type="email" bind:value={form.email} />
		{#if validation.errors.email}
			<span class="error">{validation.errors.email}</span>
		{:else if validation.warnings.email}
			<span class="warning">{validation.warnings.email}</span>
		{/if}
	</div>

	<div class="field">
		<label>Password</label>
		<input type="password" bind:value={form.password} />
		{#if validation.errors.password}
			<span class="error">{validation.errors.password}</span>
		{/if}
	</div>

	<div class="field">
		<label>Confirm Password</label>
		<input type="password" bind:value={form.confirmPassword} />
		{#if validation.errors.confirmPassword}
			<span class="error">{validation.errors.confirmPassword}</span>
		{/if}
	</div>

	<div class="field">
		<label>Age</label>
		<input type="number" bind:value={form.age} />
		{#if validation.errors.age}
			<span class="error">{validation.errors.age}</span>
		{:else if validation.warnings.age}
			<span class="warning">{validation.warnings.age}</span>
		{/if}
	</div>

	<div class="field">
		<label>Website (optional)</label>
		<input type="text" bind:value={form.website} />
		{#if validation.errors.website}
			<span class="error">{validation.errors.website}</span>
		{:else if validation.warnings.website}
			<span class="warning">{validation.warnings.website}</span>
		{/if}
	</div>

	<button type="submit" disabled={!validation.isValid}>
		Submit ({validation.errorCount} errors, {validation.warningCount} warnings)
	</button>
</form>

3. Financial Report Generator

<script>
	let transactions = $state([
		{ date: '2024-01-05', type: 'income', amount: 5000, category: 'salary' },
		{ date: '2024-01-10', type: 'expense', amount: 1200, category: 'rent' },
		{ date: '2024-01-12', type: 'expense', amount: 85, category: 'utilities' },
		{ date: '2024-01-15', type: 'income', amount: 500, category: 'freelance' },
		{ date: '2024-01-18', type: 'expense', amount: 150, category: 'groceries' },
		{ date: '2024-01-22', type: 'expense', amount: 60, category: 'entertainment' },
		{ date: '2024-01-25', type: 'expense', amount: 200, category: 'groceries' },
		{ date: '2024-01-28', type: 'income', amount: 250, category: 'dividends' }
	])

	let report = $derived.by(() => {
		// Initialize accumulators
		let totalIncome = 0
		let totalExpenses = 0
		const incomeByCategory = {}
		const expensesByCategory = {}
		const dailyBalance = []

		// Track running balance for chart data
		let runningBalance = 0

		// Sort transactions by date for running balance
		const sortedTransactions = [...transactions].sort((a, b) => new Date(a.date) - new Date(b.date))

		// Single pass through data
		for (const t of sortedTransactions) {
			if (t.type === 'income') {
				totalIncome += t.amount
				incomeByCategory[t.category] = (incomeByCategory[t.category] || 0) + t.amount
				runningBalance += t.amount
			} else {
				totalExpenses += t.amount
				expensesByCategory[t.category] = (expensesByCategory[t.category] || 0) + t.amount
				runningBalance -= t.amount
			}

			dailyBalance.push({
				date: t.date,
				balance: runningBalance,
				transaction: t
			})
		}

		// Calculate derived metrics
		const netIncome = totalIncome - totalExpenses
		const savingsRate = totalIncome > 0 ? ((netIncome / totalIncome) * 100).toFixed(1) : 0

		// Find largest expense category
		const largestExpenseCategory = Object.entries(expensesByCategory).sort(
			([, a], [, b]) => b - a
		)[0]

		// Monthly comparison (if we had previous month data)
		const expenseBreakdown = Object.entries(expensesByCategory)
			.map(([category, amount]) => ({
				category,
				amount,
				percentage: ((amount / totalExpenses) * 100).toFixed(1)
			}))
			.sort((a, b) => b.amount - a.amount)

		// Budget warnings
		const warnings = []
		if (savingsRate < 10) {
			warnings.push('Savings rate below recommended 10%')
		}
		if (expensesByCategory.entertainment > totalIncome * 0.1) {
			warnings.push('Entertainment exceeds 10% of income')
		}

		return {
			totalIncome,
			totalExpenses,
			netIncome,
			savingsRate,
			incomeByCategory,
			expensesByCategory,
			expenseBreakdown,
			largestExpenseCategory: largestExpenseCategory
				? { name: largestExpenseCategory[0], amount: largestExpenseCategory[1] }
				: null,
			dailyBalance,
			currentBalance: runningBalance,
			transactionCount: transactions.length,
			warnings
		}
	})
</script>

<h2>Financial Report</h2>

<div class="summary-cards">
	<div class="card income">
		<h3>Income</h3>
		<p class="amount">${report.totalIncome.toLocaleString()}</p>
	</div>
	<div class="card expenses">
		<h3>Expenses</h3>
		<p class="amount">${report.totalExpenses.toLocaleString()}</p>
	</div>
	<div
		class="card balance"
		class:positive={report.netIncome >= 0}
		class:negative={report.netIncome < 0}
	>
		<h3>Net</h3>
		<p class="amount">${report.netIncome.toLocaleString()}</p>
	</div>
</div>

<p>Savings Rate: {report.savingsRate}%</p>

{#if report.largestExpenseCategory}
	<p>
		Largest expense: {report.largestExpenseCategory.name} (${report.largestExpenseCategory.amount})
	</p>
{/if}

<h3>Expense Breakdown</h3>
{#each report.expenseBreakdown as item}
	<div class="expense-bar">
		<span>{item.category}</span>
		<div class="bar" style="width: {item.percentage}%"></div>
		<span>${item.amount} ({item.percentage}%)</span>
	</div>
{/each}

{#if report.warnings.length > 0}
	<div class="warnings">
		<h3>Warnings</h3>
		<ul>
			{#each report.warnings as warning}
				<li>{warning}</li>
			{/each}
		</ul>
	</div>
{/if}

4. Markdown Parser with Error Handling

<script>
	let markdownInput = $state(`# Hello World

This is a **bold** statement and this is *italic*.

- Item 1
- Item 2
- Item 3

> A blockquote

\`\`\`javascript
const x = 42;
\`\`\`
`)

	let parsed = $derived.by(() => {
		try {
			let html = markdownInput

			// Track what we've processed for stats
			const stats = {
				headings: 0,
				bold: 0,
				italic: 0,
				lists: 0,
				codeBlocks: 0,
				blockquotes: 0
			}

			// Process code blocks first (to avoid processing their contents)
			const codeBlocks = []
			html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (match, lang, code) => {
				stats.codeBlocks++
				const placeholder = `__CODE_BLOCK_${codeBlocks.length}__`
				codeBlocks.push({ lang, code: code.trim() })
				return placeholder
			})

			// Headings
			html = html.replace(/^### (.+)$/gm, (_, text) => {
				stats.headings++
				return `<h3>${text}</h3>`
			})
			html = html.replace(/^## (.+)$/gm, (_, text) => {
				stats.headings++
				return `<h2>${text}</h2>`
			})
			html = html.replace(/^# (.+)$/gm, (_, text) => {
				stats.headings++
				return `<h1>${text}</h1>`
			})

			// Bold and italic
			html = html.replace(/\*\*(.+?)\*\*/g, (_, text) => {
				stats.bold++
				return `<strong>${text}</strong>`
			})
			html = html.replace(/\*(.+?)\*/g, (_, text) => {
				stats.italic++
				return `<em>${text}</em>`
			})

			// Lists
			html = html.replace(/^- (.+)$/gm, (_, text) => {
				stats.lists++
				return `<li>${text}</li>`
			})
			html = html.replace(/(<li>.*<\/li>\n?)+/g, (match) => {
				return `<ul>${match}</ul>`
			})

			// Blockquotes
			html = html.replace(/^> (.+)$/gm, (_, text) => {
				stats.blockquotes++
				return `<blockquote>${text}</blockquote>`
			})

			// Restore code blocks
			codeBlocks.forEach((block, i) => {
				const highlighted = `<pre><code class="language-${block.lang}">${escapeHtml(block.code)}</code></pre>`
				html = html.replace(`__CODE_BLOCK_${i}__`, highlighted)
			})

			// Paragraphs (lines that aren't already wrapped)
			html = html
				.split('\n\n')
				.map((block) => {
					if (block.match(/^<[^>]+>/)) return block
					if (block.trim()) return `<p>${block}</p>`
					return ''
				})
				.join('\n')

			return {
				success: true,
				html,
				stats,
				error: null
			}
		} catch (error) {
			return {
				success: false,
				html: '',
				stats: null,
				error: error.message
			}
		}
	})

	function escapeHtml(text) {
		return text
			.replace(/&/g, '&amp;')
			.replace(/</g, '&lt;')
			.replace(/>/g, '&gt;')
			.replace(/"/g, '&quot;')
	}
</script>

<div class="editor">
	<textarea bind:value={markdownInput}></textarea>

	<div class="preview">
		{#if parsed.success}
			{@html parsed.html}

			<div class="stats">
				<small>
					{parsed.stats.headings} headings •
					{parsed.stats.bold} bold •
					{parsed.stats.italic} italic •
					{parsed.stats.codeBlocks} code blocks
				</small>
			</div>
		{:else}
			<div class="error">
				Parse error: {parsed.error}
			</div>
		{/if}
	</div>
</div>

5. Smart Data Grouping and Aggregation

<script>
	let employees = $state([
		{ id: 1, name: 'Alice', department: 'Engineering', salary: 95000, startDate: '2020-03-15' },
		{ id: 2, name: 'Bob', department: 'Marketing', salary: 72000, startDate: '2021-06-01' },
		{ id: 3, name: 'Carol', department: 'Engineering', salary: 88000, startDate: '2019-11-20' },
		{ id: 4, name: 'David', department: 'Sales', salary: 68000, startDate: '2022-01-10' },
		{ id: 5, name: 'Eve', department: 'Engineering', salary: 102000, startDate: '2018-07-22' },
		{ id: 6, name: 'Frank', department: 'Marketing', salary: 78000, startDate: '2020-09-05' },
		{ id: 7, name: 'Grace', department: 'Sales', salary: 71000, startDate: '2021-03-18' }
	])

	let groupBy = $state('department')

	let analysis = $derived.by(() => {
		const groups = {}
		const companyStats = {
			totalEmployees: employees.length,
			totalSalary: 0,
			avgSalary: 0,
			seniorityDistribution: { '<1yr': 0, '1-2yr': 0, '2-3yr': 0, '3+yr': 0 }
		}

		const now = new Date()

		// First pass: group employees and gather company-wide stats
		for (const emp of employees) {
			// Group by selected field
			const groupKey = emp[groupBy]
			if (!groups[groupKey]) {
				groups[groupKey] = {
					employees: [],
					totalSalary: 0,
					count: 0
				}
			}
			groups[groupKey].employees.push(emp)
			groups[groupKey].totalSalary += emp.salary
			groups[groupKey].count++

			// Company-wide stats
			companyStats.totalSalary += emp.salary

			// Seniority calculation
			const startDate = new Date(emp.startDate)
			const yearsEmployed = (now - startDate) / (365.25 * 24 * 60 * 60 * 1000)

			if (yearsEmployed < 1) companyStats.seniorityDistribution['<1yr']++
			else if (yearsEmployed < 2) companyStats.seniorityDistribution['1-2yr']++
			else if (yearsEmployed < 3) companyStats.seniorityDistribution['2-3yr']++
			else companyStats.seniorityDistribution['3+yr']++
		}

		companyStats.avgSalary = companyStats.totalSalary / companyStats.totalEmployees

		// Second pass: calculate per-group statistics
		const groupStats = []
		for (const [name, data] of Object.entries(groups)) {
			const salaries = data.employees.map((e) => e.salary).sort((a, b) => a - b)
			const avgSalary = data.totalSalary / data.count

			// Find newest and most senior employee
			const sorted = [...data.employees].sort(
				(a, b) => new Date(a.startDate) - new Date(b.startDate)
			)

			groupStats.push({
				name,
				count: data.count,
				totalSalary: data.totalSalary,
				avgSalary,
				minSalary: salaries[0],
				maxSalary: salaries[salaries.length - 1],
				salaryRange: salaries[salaries.length - 1] - salaries[0],
				topEarner: data.employees.reduce((top, emp) => (emp.salary > top.salary ? emp : top)),
				mostSenior: sorted[0],
				newest: sorted[sorted.length - 1],
				percentOfCompany: ((data.count / companyStats.totalEmployees) * 100).toFixed(1)
			})
		}

		// Sort groups by count (largest first)
		groupStats.sort((a, b) => b.count - a.count)

		return {
			groups: groupStats,
			company: companyStats,
			groupCount: groupStats.length
		}
	})
</script>

<h2>Employee Analysis</h2>

<label>
	Group by:
	<select bind:value={groupBy}>
		<option value="department">Department</option>
	</select>
</label>

<div class="company-overview">
	<h3>Company Overview</h3>
	<p>Total Employees: {analysis.company.totalEmployees}</p>
	<p>Total Payroll: ${analysis.company.totalSalary.toLocaleString()}</p>
	<p>Average Salary: ${analysis.company.avgSalary.toLocaleString()}</p>
</div>

<div class="seniority">
	<h4>Seniority Distribution</h4>
	{#each Object.entries(analysis.company.seniorityDistribution) as [range, count]}
		<span>{range}: {count}</span>
	{/each}
</div>

<h3>By {groupBy} ({analysis.groupCount} groups)</h3>

{#each analysis.groups as group}
	<details open>
		<summary>
			{group.name} - {group.count} employees ({group.percentOfCompany}% of company)
		</summary>

		<div class="group-stats">
			<p>Salary range: ${group.minSalary.toLocaleString()} - ${group.maxSalary.toLocaleString()}</p>
			<p>Average: ${Math.round(group.avgSalary).toLocaleString()}</p>
			<p>Top earner: {group.topEarner.name} (${group.topEarner.salary.toLocaleString()})</p>
			<p>Most senior: {group.mostSenior.name} (since {group.mostSenior.startDate})</p>
			<p>Newest: {group.newest.name} (since {group.newest.startDate})</p>
		</div>
	</details>
{/each}

6. Status Display with Switch

<script>
	let orderStatus = $state('processing')

	let statusDisplay = $derived.by(() => {
		switch (orderStatus) {
			case 'pending':
				return {
					icon: '',
					label: 'Pending',
					color: '#f39c12',
					description: 'Your order is waiting to be processed',
					actions: ['Cancel Order']
				}
			case 'processing':
				return {
					icon: '⚙️',
					label: 'Processing',
					color: '#3498db',
					description: 'We are preparing your order',
					actions: ['Contact Support']
				}
			case 'shipped':
				return {
					icon: '📦',
					label: 'Shipped',
					color: '#9b59b6',
					description: 'Your order is on its way',
					actions: ['Track Package', 'Contact Support']
				}
			case 'delivered':
				return {
					icon: '',
					label: 'Delivered',
					color: '#27ae60',
					description: 'Your order has been delivered',
					actions: ['Leave Review', 'Return Item', 'Buy Again']
				}
			case 'cancelled':
				return {
					icon: '',
					label: 'Cancelled',
					color: '#e74c3c',
					description: 'This order was cancelled',
					actions: ['View Refund Status', 'Reorder']
				}
			default:
				return {
					icon: '',
					label: 'Unknown',
					color: '#95a5a6',
					description: 'Status unknown',
					actions: ['Contact Support']
				}
		}
	})
</script>

<div class="status-card" style="border-color: {statusDisplay.color}">
	<div class="status-header" style="background: {statusDisplay.color}">
		<span class="icon">{statusDisplay.icon}</span>
		<span class="label">{statusDisplay.label}</span>
	</div>
	<p>{statusDisplay.description}</p>
	<div class="actions">
		{#each statusDisplay.actions as action}
			<button>{action}</button>
		{/each}
	</div>
</div>

<label>
	Test different statuses:
	<select bind:value={orderStatus}>
		<option value="pending">Pending</option>
		<option value="processing">Processing</option>
		<option value="shipped">Shipped</option>
		<option value="delivered">Delivered</option>
		<option value="cancelled">Cancelled</option>
	</select>
</label>

Common Mistakes with $derived.by

1. Using $derived.by When $derived Would Suffice

<script>
	let count = $state(0)

	// AVOID: Overly verbose - don't do this
	let doubled = $derived.by(() => {
		return count * 2
	})

	// PREFERRED: Simple and clean
	let doubled = $derived(count * 2)
</script>

2. Side Effects in $derived.by

Derived Values Must Be Pure

Never put side effects inside $derived or $derived.by — no fetch, no console.log, no mutations to state you don’t own. Derived values are recomputed whenever Svelte decides to, so side effects will fire unpredictably. Move them to $effect.

Derived values should be pure computations. Don’t put side effects inside them:

<script>
	let count = $state(0)

	// AVOID: Side effects in derived
	let doubled = $derived.by(() => {
		console.log('Calculating doubled...') // Side effect!
		fetch('/api/log', { method: 'POST' }) // Side effect!
		someGlobalVariable = count // Side effect!
		return count * 2
	})

	// PREFERRED: Pure computation only
	let doubled = $derived(count * 2)

	// Put side effects in $effect instead
	$effect(() => {
		console.log('Count changed to:', count)
	})
</script>

3. Async Operations in $derived.by

$derived.by Cannot Be Async

$derived.by must return a value synchronously. You cannot await inside it. For async data, use $state + $effect with an AbortController to handle stale responses.

$derived.by must be synchronous. It cannot be async:

<script>
	let userId = $state(1)

	// AVOID: This won't work - can't use async/await
	let user = $derived.by(async () => {
		const response = await fetch(`/api/users/${userId}`)
		return response.json()
	})

	// PREFERRED: For async data, use $effect and $state together
	let user = $state(null)
	let loading = $state(false)

	$effect(() => {
		loading = true
		fetch(`/api/users/${userId}`)
			.then((r) => r.json())
			.then((data) => {
				user = data
				loading = false
			})
	})
</script>

4. Forgetting the Return Statement

<script>
	let items = $state([1, 2, 3])

	// AVOID: Missing return - result is undefined!
	let sum = $derived.by(() => {
		let total = 0
		for (const item of items) {
			total += item
		}
		// Oops! Forgot to return
	})

	// PREFERRED: Always return your computed value
	let sum = $derived.by(() => {
		let total = 0
		for (const item of items) {
			total += item
		}
		return total
	})
</script>

Part 3: $derived vs $derived.by — The Differences Explained

Now that you’ve seen both forms in action, let’s clarify exactly when to use each one.

The Technical Relationship

Under the hood, $derived(expression) is syntactic sugar for $derived.by(() => expression). The compiler transforms:

let doubled = $derived(count * 2)

Into something conceptually like:

let doubled = $derived.by(() => count * 2)

They’re the same at runtime—the difference is purely in what syntax is allowed.

Side-by-Side Comparison

Feature$derived(expression)$derived.by(() => { ... })
InputSingle expressionFunction body
StatementsOne onlyMultiple allowed
Local variables❌ Not possible✅ Allowed
Control flowOnly ternary ? :if/else, switch, loops
Early returns❌ Not possible✅ Allowed
Try/catch❌ Not possible✅ Allowed
ReadabilityConcise, obviousMore verbose
Use caseSimple formulasComplex logic
Intent signal“This is straightforward”“This has complexity”

Decision Flowchart

Start


Can you write it as a single expression
(no semicolons, no { } curly braces)?

  ├── YES → Use $derived(expression)

  └── NO → Continue...


      Do you need any of these?
      • Multiple statements
      • Intermediate variables
      • if/else or switch
      • for/while loops
      • try/catch
      • Early returns

         ├── YES → Use $derived.by(() => { ... })

         └── NO → You probably can use $derived()
                  Just write it as one expression

Examples: Same Result, Different Approaches

Sometimes you can achieve the same result with either form. Choose based on readability:

<script>
	let numbers = $state([1, 2, 3, 4, 5])

	// Both work - choose the clearer one

	// Simple enough for $derived
	let sum = $derived(numbers.reduce((a, b) => a + b, 0))
	let avg = $derived(sum / numbers.length)

	// Or use $derived.by for more clarity
	let stats = $derived.by(() => {
		const sum = numbers.reduce((a, b) => a + b, 0)
		const avg = sum / numbers.length
		return { sum, avg }
	})
</script>
<script>
	let user = $state({ role: 'admin' })

	// Ternary works for simple conditions
	let isAdmin = $derived(user.role === 'admin' ? true : false)
	// Even simpler:
	let isAdmin = $derived(user.role === 'admin')

	// But complex conditions need $derived.by
	let accessLevel = $derived.by(() => {
		if (user.role === 'admin') return 'full'
		if (user.role === 'editor') return 'write'
		if (user.role === 'viewer') return 'read'
		return 'none'
	})
</script>

When $derived Gets Awkward

If you find yourself writing complex expressions like this, switch to $derived.by:

<script>
	let cart = $state([])

	// AVOID: This works but is hard to read
	let summary = $derived(
		cart.length === 0
			? { empty: true, total: 0, message: 'Cart is empty' }
			: {
					empty: false,
					total: cart.reduce((sum, item) => sum + item.price * item.quantity, 0),
					message: `${cart.length} items in cart`
				}
	)

	// PREFERRED: Much clearer with $derived.by
	let summary = $derived.by(() => {
		if (cart.length === 0) {
			return { empty: true, total: 0, message: 'Cart is empty' }
		}

		const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0)

		return {
			empty: false,
			total,
			message: `${cart.length} items in cart`
		}
	})
</script>

Performance Considerations

Both forms have identical performance. The choice is purely about code clarity and maintainability. Svelte’s compiler handles both the same way.

However, keep the computation itself efficient:

<script>
	let hugeArray = $state([...Array(100000).keys()])

	// AVOID: This runs every time hugeArray changes
	let sorted = $derived([...hugeArray].sort())

	// Consider if you really need this to be reactive,
	// or if you can compute it once or less frequently
</script>

Advanced Topics

How Dependency Tracking Works

Svelte tracks dependencies at runtime, not by analyzing your code. It observes which reactive values are actually read during execution.

<script>
	let showDetails = $state(false)
	let user = $state({
		name: 'Alice',
		email: 'alice@example.com',
		bio: 'Software developer'
	})

	let display = $derived(showDetails ? `${user.name} - ${user.email}: ${user.bio}` : user.name)
</script>

When showDetails is false:

  • The false branch executes: user.name
  • Dependencies: showDetails, user.name
  • Changing user.email or user.bio does NOT recalculate display!

When showDetails is true:

  • The true branch executes: ${user.name} - ${user.email}: ${user.bio}
  • Dependencies: showDetails, user.name, user.email, user.bio
  • Now changing any of these recalculates display

Why this matters: Svelte is smart about avoiding unnecessary work. It only recalculates when values that were actually used have changed.

Excluding Dependencies with untrack

Sometimes you want to read a reactive value without creating a dependency. The untrack function from 'svelte' lets you do this:

<script>
	import { untrack } from 'svelte'

	let count = $state(0)
	let multiplier = $state(2)

	// This derived value depends ONLY on count, not multiplier
	let result = $derived.by(() => {
		const mult = untrack(() => multiplier) // Read without tracking
		return count * mult
	})

	// Changing count → recalculates result
	// Changing multiplier → does NOT recalculate result
</script>

Use cases for untrack:

  • Configuration values that rarely change
  • Logging or debugging without creating dependencies
  • One-time reads of reactive state
  • Breaking circular dependencies

Automatic Memoization

Derived values only recalculate when their dependencies change:

<script>
	let items = $state([
		/* ... */
	])
	let filter = $state('all')

	let filtered = $derived.by(() => {
		console.log('Recalculating...') // Only logs when items or filter changes

		if (filter === 'all') return items
		return items.filter((item) => item.category === filter)
	})
</script>

If you read filtered multiple times in your template, the calculation only runs once per change.

Derived Values Are NOT Proxied

Unlike $state, $derived doesn’t wrap objects in Proxies. The returned value is as-is:

<script>
	let items = $state([
		{ name: 'A', selected: false },
		{ name: 'B', selected: true },
		{ name: 'C', selected: false }
	])

	// This finds the selected item
	let selected = $derived(items.find((item) => item.selected))
</script>

<!-- 
  selected points to the actual item in items array.
  Because items IS proxied, changing selected.name 
  actually changes items[1].name!
-->
{#if selected}
	<input bind:value={selected.name} />
{/if}

Overriding Derived Values (Optimistic UI)

Derived Values Can Be Temporarily Overridden

Assigning directly to a derived variable overrides it until the next time its source dependencies change, at which point Svelte re-derives it. This is intentional and useful for optimistic UI — but it means the override is temporary, not permanent.

Derived values can be temporarily overridden—perfect for optimistic updates:

<script>
	let { post } = $props()

	// Normally derived from the server data
	let likes = $derived(post.likes)

	async function handleLike() {
		// Immediately show the like (optimistic)
		likes++

		try {
			// Tell the server
			await fetch(`/api/posts/${post.id}/like`, { method: 'POST' })
			// Server will eventually update post.likes
		} catch (error) {
			// Failed! Roll back the optimistic update
			likes--
		}
	}
</script>

<button onclick={handleLike}>
	{likes}
</button>

The override is temporary—once post.likes changes from the server, likes will sync back to the derived value.

Destructuring with $derived

Svelte 5 handles destructuring in $derived declarations specially—each destructured variable becomes its own reactive derived value:

<script>
	let data = $state({ width: 10, height: 20 })

	// This WORKS in Svelte 5!
	// Each variable becomes its own derived value
	let { area, perimeter } = $derived({
		area: data.width * data.height,
		perimeter: 2 * (data.width + data.height)
	})
</script>

<p>Area: {area}</p><p>Perimeter: {perimeter}</p>

This is roughly equivalent to:

<script>
	let _computed = $derived({
		area: data.width * data.height,
		perimeter: 2 * (data.width + data.height)
	})
	let area = $derived(_computed.area)
	let perimeter = $derived(_computed.perimeter)
</script>

However, if you destructure the result of a $derived value after it’s created (not in the declaration), you lose reactivity:

<script>
	let data = $state({ width: 10, height: 20 })

	// AVOID: This takes a snapshot, not a reactive reference
	// area and perimeter are static values from the moment of destructuring
	let { area, perimeter } = dimensions

	// PREFERRED: Access through the derived object
	// Use dimensions.area and dimensions.perimeter in your template
	let dimensions = $derived({
		area: data.width * data.height,
		perimeter: 2 * (data.width + data.height)
	})
</script>

The key distinction:

  • let { a, b } = $derived(...) → ✅ Reactive (special compiler handling)
  • let x = $derived(...); let { a, b } = x → ❌ Not reactive (runtime destructuring)

If you need individual derived values and can’t use the declaration destructuring pattern, define them separately:

<script>
	// PREFERRED: Separate derived values
	let area = $derived(data.width * data.height)
	let perimeter = $derived(2 * (data.width + data.height))
</script>

Quick Reference

$derived — Simple Expressions

// Arithmetic
let doubled = $derived(count * 2)
let total = $derived(price + tax + shipping)

// Array methods
let filtered = $derived(items.filter((x) => x > 0))
let sorted = $derived([...items].sort())
let first = $derived(items[0])
let count = $derived(items.length)

// String operations
let upper = $derived(name.toUpperCase())
let message = $derived(`Hello, ${name}!`)

// Ternary conditions
let status = $derived(isActive ? 'Active' : 'Inactive')
let display = $derived(loading ? 'Loading...' : data)

// Chained methods
let result = $derived(
	items
		.filter((x) => x.active)
		.map((x) => x.name)
		.join(', ')
)

// Nullish coalescing
let value = $derived(user?.name ?? 'Anonymous')

$derived.by — Complex Logic

// Multiple statements and local variables
let stats = $derived.by(() => {
	const sum = items.reduce((a, b) => a + b, 0)
	const avg = sum / items.length
	return { sum, avg }
})

// If/else control flow
let tier = $derived.by(() => {
	if (points >= 1000) return 'gold'
	if (points >= 500) return 'silver'
	return 'bronze'
})

// Switch statements
let icon = $derived.by(() => {
	switch (status) {
		case 'success':
			return ''
		case 'error':
			return ''
		default:
			return ''
	}
})

// Loops
let grouped = $derived.by(() => {
	const groups = {}
	for (const item of items) {
		const key = item.category
		if (!groups[key]) groups[key] = []
		groups[key].push(item)
	}
	return groups
})

// Try/catch
let parsed = $derived.by(() => {
	try {
		return { data: JSON.parse(input), error: null }
	} catch (e) {
		return { data: null, error: e.message }
	}
})

// Early returns
let result = $derived.by(() => {
	if (!user) return null
	if (!user.verified) return { error: 'Not verified' }
	return { data: user.profile }
})

Excluding Dependencies

import { untrack } from 'svelte'

let result = $derived.by(() => {
	const config = untrack(() => configValue) // Read without tracking
	return count * config
})

Destructuring Pattern

// Reactive - special compiler handling
let { area, perimeter } = $derived({
	area: width * height,
	perimeter: 2 * (width + height)
})

// NOT reactive - runtime destructuring
let dimensions = $derived({ area: width * height })
let { area } = dimensions // loses reactivity

Key Takeaways

The $derived rune is essential for building reactive Svelte 5 applications. Here’s what you need to remember:

Use $derived When:

  • Your computation is a single expression
  • No intermediate variables are needed
  • Logic fits on one line (or chains nicely)
  • You want to signal “this is straightforward”

Use $derived.by When:

  • You need multiple statements
  • You need local variables for intermediate values
  • You need control flow (if/else, switch, loops)
  • You need try/catch error handling
  • You need early returns
  • The logic is complex enough to benefit from explicit structure

Key Principles:

  1. Choose based on complexity — Use $derived for simple formulas, $derived.by for complex logic
  2. Dependencies are tracked at runtime — Svelte only recalculates when values actually used have changed
  3. Keep computations pure — No side effects. Use $effect for side effects
  4. Derived values are memoized — They only recalculate when dependencies change
  5. Use untrack to exclude dependencies — When you need to read without creating a dependency

With these patterns mastered, you’ll write cleaner, more efficient reactive code that automatically stays in sync with your data.


See Also

Official Documentation

External Resources