# What Operations Does Fre’s Diff Algorithm Support? UPDATE, INSERT, MOVE, and REMOVE Explained

> Discover the operations Fre's diff algorithm supports: UPDATE, INSERT, MOVE, and REMOVE. Learn how Fre handles each efficiently for better performance.

- Repository: [frejs/fre](https://github.com/frejs/fre)
- Tags: internals
- Published: 2026-03-02

---

**Fre’s virtual-DOM diff algorithm produces three explicit action types—UPDATE, INSERT, and MOVE—while performing REMOVE operations immediately via direct DOM cleanup rather than queuing them in the actions array.**

Fre is a lightweight React-like framework maintained in the **frejs/fre** repository. Its reconciliation engine uses a specialized diff algorithm to minimize DOM mutations. Understanding which operations the Fre diff algorithm supports—and how it handles node removal—is essential for debugging render behavior and optimizing component performance.

## The Four TAG Operations Defined in Fre

The `TAG` enum in **src/type.ts** (lines 116–124) defines four bit-flag constants that drive the reconciliation process. However, only three of these flags appear in the `actions` array returned by the `diff` function in **src/reconcile.ts**.

### UPDATE (TAG.UPDATE)

**Value:** `1 << 1` (binary `10`)

The UPDATE operation occurs when a previous fiber and the new fiber represent the same element—specifically when both the `type` and `key` match. The algorithm clones the old fiber onto the new one and pushes an `{op: TAG.UPDATE}` entry into the actions array. This signals the commit phase to reuse the existing DOM node and apply property changes.

### INSERT (TAG.INSERT)

**Value:** `1 << 2` (binary `100`)

INSERT actions are generated when a new element appears in the children list but has no matching counterpart in the previous render. The diff algorithm pushes `{op: TAG.INSERT, cur: newFiber, ref: oldFiber}`, instructing the commit phase to create a fresh DOM node and insert it before the reference fiber.

### MOVE (TAG.MOVE)

**Value:** `1 << 6` (binary `1000000`)

MOVE operations handle reordering. When the diff algorithm encounters an existing element later in the new list (identified by its `key`), it clones the old fiber onto the new one and emits `{op: TAG.MOVE, cur: oldFiber, ref: oldFiberAtHead}`. This moves the DOM node to its new position without destroying and recreating it.

### REMOVE (TAG.REMOVE)

**Value:** Defined in the enum but **not emitted by `diff`**

Unlike UPDATE, INSERT, and MOVE, the REMOVE operation never appears in the actions array returned from the `diff` function. Instead, when an old fiber has no counterpart in the new children list, the algorithm calls `removeElement(oldFiber)` directly during the reconciliation loop. The `TAG.REMOVE` flag is later set on the removed fiber in **src/commit.ts** (line 64) to coordinate cleanup, but the diff phase itself does not generate a REMOVE action.

## How the Diff Algorithm Generates Actions

The core diff logic resides in **src/reconcile.ts** (approximately lines 40–103). This implementation uses a while-loop to compare the old fiber list against the new fiber list, generating actions based on key matching and element type comparisons.

The algorithm prioritizes in-place updates for matching keys. When keys differ, it searches the remaining old fibers for a match. Finding a match triggers a MOVE action; failing to find one triggers an INSERT. Elements that remain unmatched in the old list after the loop completes are passed directly to `removeElement` for immediate destruction.

## Practical Code Examples

### Detecting a MOVE Operation

When sibling elements swap positions, Fre emits MOVE actions to reorder the DOM efficiently:

```tsx
import { reconcile } from 'fre'

function Reorder({ swap }: { swap: boolean }) {
  return swap ? (
    <>
      <div key="b">B</div>
      <div key="a">A</div>
    </>
  ) : (
    <>
      <div key="a">A</div>
      <div key="b">B</div>
    </>
  )
}

// Initial render
const root = document.getElementById('root')
reconcile(root, <Reorder swap={false} />)

// Update triggers MOVE for both fibers
reconcile(root, <Reorder swap={true} />)

```

In **src/reconcile.ts**, the diff detects that keys `"a"` and `"b"` exist in both lists but in different positions. It clones the old fibers and pushes `{op: TAG.MOVE, cur: oldFiber, ref: oldFiberAtHead}` for each, moving the existing DOM nodes rather than recreating them.

### Handling INSERT for New Elements

Adding a new child generates an INSERT action:

```tsx
reconcile(root, (
  <>
    <span key="existing">First</span>
  </>
))

// Later...
reconcile(root, (
  <>
    <span key="existing">First</span>
    <span key="new">Second</span>  {/* INSERT action generated */}
  </>
))

```

Because the fiber with key `"new"` has no corresponding old fiber, the diff in **src/reconcile.ts** pushes `{op: TAG.INSERT, cur: newFiber, ref: oldFiber}`, causing the commit phase to append a new DOM node.

### Processing UPDATE for Attribute Changes

When a component’s props change but its key and type remain constant, the algorithm issues an UPDATE:

```tsx
reconcile(root, <input key="main" value="initial" />)

// Later...
reconcile(root, <input key="main" value="updated" />)

```

The matching `key` (`"main"`) and `type` (`input`) cause the diff to clone the old fiber and emit `{op: TAG.UPDATE}`. The commit phase updates the `value` attribute on the existing DOM node without replacing the element.

### REMOVE Handling Outside the Diff Loop

Deletions bypass the actions array entirely:

```tsx
reconcile(root, (
  <>
    <p key="keep">Keep this</p>
    <p key="delete">Delete this</p>
  </>
))

// Later...
reconcile(root, (
  <>
    <p key="keep">Keep this</p>  {/* "delete" fiber removed immediately */}
  </>
))

```

When processing the second render, the diff loop in **src/reconcile.ts** finds no match for the `"delete"` key. Instead of creating a REMOVE action, it invokes `removeElement(oldFiber)` immediately. The fiber’s flag is set to `TAG.REMOVE` in **src/commit.ts** to ensure proper cleanup of effects and refs, but this happens outside the diff result.

## Summary

- **UPDATE (`1 << 1`)**: Generated for matching `type` and `key` pairs, enabling in-place property updates in **src/reconcile.ts**.
- **INSERT (`1 << 2`)**: Generated for new fibers without corresponding old fibers, triggering DOM node creation.
- **MOVE (`1 << 6`)**: Generated when existing fibers change position, allowing efficient DOM reordering without node destruction.
- **REMOVE**: Not emitted by the diff algorithm; handled via direct `removeElement` calls during reconciliation, with the `TAG.REMOVE` flag applied in **src/commit.ts** for cleanup coordination.

## Frequently Asked Questions

### Does Fre’s diff algorithm generate a REMOVE action in the actions array?

No. According to the source code in **src/reconcile.ts**, the diff loop calls `removeElement(oldFiber)` directly when an old fiber lacks a matching new fiber. The `TAG.REMOVE` flag is set later in **src/commit.ts** (line 64), but it never appears in the `actions` array returned from `diff`.

### What distinguishes a MOVE operation from an UPDATE operation in Fre?

A **MOVE** operation (`TAG.MOVE`, `1 << 6`) occurs when an element’s `key` and `type` match but its position in the children list changes, requiring DOM node repositioning. An **UPDATE** operation (`TAG.UPDATE`, `1 << 1`) occurs when the element maintains the same position, requiring only attribute or content modifications.

### How does Fre handle insertions of new elements during reconciliation?

When the diff algorithm encounters a new fiber with no matching old counterpart in **src/reconcile.ts**, it generates an `{op: TAG.INSERT, cur: newFiber, ref: oldFiber}` action. The commit phase then creates a new DOM node and inserts it before the reference fiber.

### Where are the TAG constants defined in the Fre repository?

The `TAG` enum defining UPDATE, INSERT, REMOVE, and MOVE constants lives in **src/type.ts** at lines 116–124. The core logic that consumes these flags is implemented in **src/reconcile.ts**, while the actual DOM removal and flag application occurs in **src/commit.ts**.