If you’re exploring Lodestar’s SSZ library and you’ve come across terms like view, subview, or TreeView, you might be wondering — what exactly is a subview and why do I need it?
Let’s break it down step-by-step, starting from the basics.
SSZ (Simple Serialize) is Ethereum’s serialization format.
It defines how Ethereum’s consensus data (like blocks, states, attestations) are stored, hashed, and transmitted.
In Lodestar (a TypeScript implementation of Ethereum’s consensus layer), SSZ data isn’t stored as plain JavaScript objects.
Instead, Lodestar uses Views — special objects that represent SSZ data along with its Merkle tree.
This helps:
Make updates efficient (only re-hash the changed part of the tree)
Support proofs (Merkle branches)
Keep large data structures consistent
A View is Lodestar’s in-memory representation of an SSZ value.
It behaves like a normal JavaScript object (you can get/set properties).
But under the hood, it’s backed by a Merkle tree (a hash-based data structure).
(If you are not sure what merkle trees are or merkleization , I will post a beginner friendly article explaining those concepts in details.)
There are two main kinds:
View Type | Description |
|---|---|
TreeView | Tree-backed view; any changes are immediately committed to the tree. |
TreeViewDU | “Delayed Update” view; changes are cached and committed only when you call |
You create views like this:
import { ssz } from "@lodestar/types";
const attestation = ssz.phase0.Attestation.defaultView();
Now attestation is a View of type Attestation.
A Subview is a nested view — a field inside another view that’s itself a complex SSZ object.
Think of it as a branch inside a bigger Merkle tree.
Example:
const attestation = ssz.phase0.Attestation.defaultView(); //the main view
attestation.data // ← This is a subview
attestation.data.source // ← Another subview
attestation.signature // ← Not a subview (it's a byte vector)
Visually, you can think of it like this:
Attestation (root view)
├── aggregationBits (basic type)
├── data (Subview)
│ ├── source (Subview)
│ │ ├── epoch (basic type)
│ │ └── root (basic type)
│ └── target (Subview)
└── signature (basic type)
In Lodestar, each nested SSZ type (like a container, list, or vector) automatically becomes a subview.
Subviews make it possible to work with parts of large SSZ structures efficiently — without touching or re-hashing the entire thing.
Let’s see what this means in practice.
Instead of treating an entire object as a single blob, you can zoom into just one part.
// Create an attestation view
const att = ssz.phase0.Attestation.defaultView();
// Change a nested field deep inside
att.data.source.epoch = 10;
Under the hood, Lodestar:
Updates only the
sourcebranch of the treeRe-hashes just the affected nodes
Updates the root automatically
No need to rebuild the whole object.
Subviews let you copy or replace entire parts of a structure easily.
Example:
const att1 = ssz.phase0.Attestation.defaultView();
const att2 = ssz.phase0.Attestation.defaultView();
// Copy `data` from att1 to att2
att2.data = att1.data;
Here you replaced the entire data subtree — including source, target, and everything under it — in one line.
This is much faster than serializing/deserializing plain JS objects.
Subviews are essential for Merkle proofs, since they represent subtrees.
For example:
const proof = attestation.createProof([
['data', 'source', 'epoch']
]);
Here, Lodestar knows exactly how to navigate the tree to isolate data → source → epoch — because each is a subview pointing to a subtree.
There’s a subtle but important behavior difference between TreeView and TreeViewDU when it comes to subviews.
Each subview is independent.
If you assign one subview to another, Lodestar copies the data, but they don’t stay linked.
const c1 = C.toView({ a: [0, 0] });
const c2 = C.toView({ a: [1, 1] });
// Copies data — not linked
c1.a = c2.a;
// Changing c1 doesn’t affect c2
c1.a.set(0, 5);
console.log(c2.a.get(0)); // still 1
In TreeViewDU, subviews share mutable caches for performance.
So assigning one subview to another links them until you commit.
const c1 = C.toViewDU({ a: [0, 0] });
const c2 = C.toViewDU({ a: [1, 1] });
// Now both reference the same cache
c1.a = c2.a;
// Mutations affect both
c1.a.set(0, 5);
console.log(c2.a.get(0)); // 5
That’s because DU views prioritize speed and batching over isolation.
Changes stay in cache until you finalize them with .commit().
Use Case | Why Subviews Help |
|---|---|
Large SSZ structures | Update or access only parts without rehashing the whole thing |
Proof generation | Each subview corresponds to a Merkle subtree — easy to prove |
State updates | Replace only a section of state efficiently |
Data reuse | Copy a subtree (e.g. |
Testing | Inspect or mutate inner structures directly |
Here’s a quick end-to-end example showing subviews in action:
import { ssz } from "@lodestar/types";
// Create a tree-backed view
const att = ssz.phase0.Attestation.defaultView();
// Work with nested data (subviews)
att.data.source.epoch = 5;
att.data.target.epoch = 8;
// Replace a subtree
const newData = ssz.phase0.AttestationData.defaultView();
newData.source.epoch = 10;
att.data = newData;
// Create a proof using subviews
const proof = att.createProof([
['data', 'source', 'epoch'],
]);
console.log(att.hashTreeRoot().toString());
Concept | Meaning |
|---|---|
View | Lodestar’s representation of SSZ data in memory (tree-backed). |
Subview | A nested view (a field that is itself an SSZ type). |
Why it matters | Enables efficient updates, subtree reuse, and proofs. |
TreeView vs TreeViewDU | Independent vs linked subviews (immediate vs delayed updates). |
Practical uses | Efficient state management, proof generation, data reuse. |
A Subview is just a “view within a view” — a nested SSZ structure you can access and manipulate independently.
It’s the bridge between Lodestar’s Merkle trees and the object-oriented way TypeScript developers write code.
Thank you for taking your time to read through this article and if you have any question feel free to reach out. More resources on SSZ here.
An old video but still important/relevant.

