Knockout Pure Computeds: When Lazy Evaluation Actually Pays Off
Pure computeds in Knockout skip work when nobody is watching, but they forbid side effects and can surprise you in dependency chains. Here is how to decide.
11 Sept 2026, 23:09 UTC

The hidden cost of an always-on computed
In Knockout, an observable is a value wrapper that notifies the UI when it changes. A computed derives a value from other observables and re-evaluates when those dependencies change. You build a dashboard with a live clock and a filtered list. The clock updates every second. Your filtered list does not care about the time, so Knockout does not re-run it when the clock ticks. That part works. The problem appears when the filtered list is expensive—say it maps over thousands of records—and the UI section displaying it is currently hidden. A regular ko.computed still evaluates whenever its dependencies change, even if no element is bound to it. You pay for work nobody sees.
What ko.pureComputed changes
Knockout 3.5.0 introduced ko.pureComputed. A pure computed is a computed observable that promises to be free of side effects. Because Knockout knows re-running it cannot change the outside world, it can put the computed to sleep when no subscribers exist. While asleep, dependency changes are recorded but evaluation is deferred until someone subscribes again. This is the core difference: a regular computed stays awake to maintain its value; a pure computed only works when someone is listening.
The promise has teeth. If you attempt to write to an observable from inside a pure computed, Knockout throws an error. That restriction is what makes the optimization safe.
A todo list with a ticking clock
Consider a todo view model. Each item has a done flag. You want a count of open items. You also have a now observable updated once per second.
function TodoItem(title) {
this.title = ko.observable(title);
this.done = ko.observable(false);
}
function TodoViewModel() {
this.items = ko.observableArray([
new TodoItem("Draft Knockout article"),
new TodoItem("Merge feature branch")
]);
// Regular computed: evaluates whenever items or done flags change
this.openCountRegular = ko.computed(function() {
return this.items().filter(function(item) {
return !item.done();
}).length;
}, this);
// Pure computed: sleeps when the bound element is removed
this.openCountPure = ko.pureComputed(function() {
return this.items().filter(function(item) {
return !item.done();
}).length;
}, this);
this.now = ko.observable(new Date().toLocaleTimeString());
setInterval(function() {
this.now(new Date().toLocaleTimeString());
}.bind(this), 1000);
}
ko.applyBindings(new TodoViewModel());
Bind the count to a visible element:
<p>Open items: <span data-bind="text: openCountPure"></span></p>
While that element exists, openCountPure has a subscriber and behaves much like a regular computed: it re-evaluates when an item's done flag changes. The now observable is not a dependency, so the timer never triggers it.
Now wrap the display in a conditional:
<div data-bind="if: showCount">
<p>Open items: <span data-bind="text: openCountPure"></span></p>
</div>
When showCount becomes false, Knockout disposes the binding and the subscription. openCountPure goes to sleep. If you then flip ten done flags, a regular computed would re-run ten times. The pure computed records that its dependencies changed but does not evaluate until showCount becomes true again.
Trade-offs, sharp edges, and how to verify
Pure computeds are not free. They still evaluate once when they wake up, so the first render after a subscription cannot be avoided. If you chain a pure computed into a regular computed, be careful: the regular computed may read the pure computed's stale value before the pure computed has re-evaluated. Keep dependency chains consistent—either all pure or all regular for a given derivation path.
They also enforce a stricter contract. You cannot update another observable, trigger a network request, or manipulate the DOM from inside a pure computed. If your derivation needs to push a side effect, use a regular computed or move the side effect into a subscription.
You can verify the sleeping behavior in any page using Knockout 3.5.0 or later. Add a counter inside the pure computed:
var evalCount = 0;
this.openCountPure = ko.pureComputed(function() {
evalCount++;
return this.items().filter(function(item) {
return !item.done();
}).length;
}, this);
In your browser's developer tools console, toggle the conditional binding that controls the subscription. Watch evalCount. It should increment only while the element is bound and only when items or a done flag changes—not on every timer tick. The browser's performance timeline can confirm the reduced scripting load during the sleeping period.
Reach for a regular ko.computed when the derivation is cheap, always visible, or needs side effects. Choose ko.pureComputed when the calculation is expensive and the UI may hide or dispose of the bound element. The win is not that pure computeds magically skip unrelated dependencies—both computed types ignore non-dependencies. The win is that pure computeds stop working entirely when nobody is watching, and Knockout can guarantee that safely because they promise not to touch the world around them.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.