Skip to content

Knockout.js Observables — Dependency Tracking and Change Notification

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Knockout.js Observables. We cover key concepts, practical examples, and best practices to help you master this topic.

Knockout.js observables are function-based reactive properties that notify all subscribers whenever their value changes, enabling automatic UI synchronization without polling or manual event wiring.

What You'll Learn

  • Creating observables with ko.observable()
  • Reading and writing observable values
  • Understanding the subscriber-notification mechanism
  • Chaining write operations and using peek
  • When to use writeable observables vs computed values

Why It Matters

Without observables, you would have to manually update the DOM every time data changes. Observables automate this: you change the data, and Knockout figures out which parts of the UI need refreshing and updates only those elements.

Real-World Use

A real-time stock ticker where prices update every second. Each price is an observable; when the Websocket pushes a new price, the observable updates, and only the price cells on the page re-render — the rest of the dashboard stays untouched.

Observable Lifecycle

flowchart LR
    A[ko.observable(initialValue)] --> B[Observable Function]
    B -->|Read: observable()| C[Returns Stored Value]
    B -->|Write: observable(newVal)| D[Notifies Subscribers]
    D --> E[Update DOM]
    D --> F[Update Computeds]
    D --> G[Update Subscriptions]
    E --> B
    style B fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px

Creating and Using Observables

An observable is created by calling ko.observable() with an optional initial value:

// Observable with initial value
var firstName = ko.observable('John');
var lastName = ko.observable('Doe');
var age = ko.observable(30);

// Observable without initial value (undefined)
var middleName = ko.observable();

// Reading values — always call as a function
console.log(firstName()); // Output: John
console.log(age());       // Output: 30
console.log(middleName());// Output: undefined

// Writing values — pass the new value as argument
firstName('Jane');
console.log(firstName()); // Output: Jane

Observable Notifications

When an observable's value changes, it notifies every subscriber synchronously. Subscribers include:

  • DOM bindings that reference the observable
  • Computed observables that depend on it
  • Manual subscriptions created with subscribe()
var counter = ko.observable(0);

// Subscribe to changes
counter.subscribe(function(newValue) {
  console.log('Counter changed to: ' + newValue);
});

counter(1); // Console: Counter changed to: 1
counter(5); // Console: Counter changed to: 5
counter(5); // No notification (value did not change)

Expected output: The subscribe callback fires only when the value actually changes. Setting the same value again produces no notification.

Chaining Write Operations

Observables return the observable instance after a write, enabling method chaining:

var settings = ko.observable({
  theme: 'dark',
  fontSize: 14,
  language: 'en'
});

// Chain multiple writes
settings(null)
  .settings({ theme: 'light', fontSize: 16, language: 'fr' });

console.log(settings().language); // Output: fr

Using peek() to Read Without Dependency

Sometimes you need to read an observable's value inside a computed without creating a dependency. The peek() method reads the value without being tracked:

var clickCount = ko.observable(0);

var logMessage = ko.computed(function() {
  // Read clickCount without creating a dependency
  var count = clickCount.peek();
  return 'Clicked ' + count + ' times. Logged at: ' + new Date().toLocaleTimeString();
});

clickCount(1); // logMessage updates because we wrote to clickCount
// But if clickCount is only read via peek(), the computed won't re-evaluate

Expected output: The computed observes the clickCount write (because a write always notifies), but subsequent reads inside the computed via peek() do not create re-evaluation dependencies.

Observable Arrays

ko.observableArray() tracks array mutation with special methods:

var items = ko.observableArray(['apple', 'banana', 'cherry']);

// Standard observableArray methods
items.push('date');        // Adds to end
items.pop();               // Removes from end
items.unshift('apricot');  // Adds to beginning
items.shift();             // Removes from beginning
items.splice(1, 1);        // Removes one item at index 1
items.sort();              // Sorts in place
items.reverse();           // Reverses in place

// Replace entire array
items(['new', 'items']);

// Read current array
console.log(items());      // Output: ['new', 'items']
console.log(items().length); // Output: 2

Expected output: Using push, pop, and other array methods triggers notifications. Native array methods like items().push() do NOT notify — always use the observableArray methods.

Extending Observables with .extend()

Knockout provides built-in extenders that modify observable behavior:

var searchQuery = ko.observable('').extend({
  rateLimit: { timeout: 300, method: 'notifyWhenChangesStop' },
  required: true
});

var count = ko.observable(0).extend({
  notify: 'always',        // Always notify, even if same value
  throttle: 100            // Limit update frequency
});

Common Mistakes

  1. Forgetting parentheses when reading - firstName returns the observable function; firstName() returns the value. Using the function where a value is expected can cause cryptic errors in bindings.

  2. Using native array methods instead of observableArray methods - items().push(x) modifies the underlying array but does NOT notify subscribers. Always use items.push(x).

  3. Mutating objects in place - person.name('Bob') notifies subscribers; person().name = 'Bob' does not, because the observable still holds the same object reference.

  4. Creating observables inside a loop - Observables created inside a for loop capture the loop variable by reference, not by value. Use a closure or let to capture correctly.

  5. Assuming equality check with objects - Observables use strict equality (===) to detect changes. A new object {a:1} is always considered different from another {a:1} because they are different references.

Practice Questions

  1. How do you read the current value of an observable without creating a dependency?
  2. What happens when you assign the same value to an observable? Does it notify subscribers?
  3. Why should you prefer observableArray.push() over observableArray().push()?
  4. What method do you use to limit how often an observable notifies subscribers?
  5. How does Knockout determine whether an observable's value has changed?

Challenge: Create a shopping cart observableArray of item objects (name, price, quantity). Implement functions to add, remove, and update items. Subscribe to the array to log every change to the console.

FAQ

Can observables hold functions?

Yes, an observable can hold any JavaScript value including functions. Call myObservable() to get the function, then invoke it: myObservable()(args).

How do I batch multiple observable changes into one notification?

Use ko.observable.valueHasMutated() or wrap changes in a computed that batches updates. The rateLimit extender on a dependent computed can also debounce updates.

What is the difference between an observable and a regular property?

A regular property has no change notification mechanism. An observable wraps a value and notifies all subscribers when the value changes, enabling automatic UI updates.

Can I make an observable read-only?

Not directly. However, you can expose a computed observable that depends on the writable observable but has no write callback, effectively making it read-only to consumers.

How many subscribers can an observable have?

There is no hard limit. Each binding, computed, and manual subscription counts as one subscriber. Performance degrades gracefully with thousands of subscribers.

Mini Project

Build a persistent settings panel where each setting (theme, fontSize, language) is an observable with rateLimit extenders. Save changes to localStorage on every update and load them on initialization.

What's Next

Observables track values, but what about derived values? Learn how computed observables automatically derive and cache values based on their dependencies.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro