Skip to content

Mutable Collections

Most of Effect’s collections (HashMap, HashSet, List, Chunk, …) are persistent: every operation returns a new structure and leaves the original untouched. That immutability is what makes them safe to share across fibers and easy to reason about, but it has a cost — each update allocates.

The Mutable* modules trade that safety for raw speed. They mutate in place: operations change the same instance you pass in (and often return it for convenient piping), so a tight loop that accumulates thousands of values does not allocate a fresh structure on every step.

Reach for a mutable collection only for local, non-shared accumulation in hot loops, where the allocation of persistent structures dominates:

  • building up a buffer or queue you will drain immediately,
  • counting / deduplicating inside a single function,
  • a private cache owned by one service.

Avoid them for shared state. They are not fiber-safe, carry no transactional guarantees, and aliasing one instance from two places means both observe the same changing data.

  • For fiber-safe mutable state inside an Effect, use Ref — not MutableRef. MutableRef is a plain synchronous cell with no concurrency protection.
  • For shareable, persistent maps and sets, use the immutable HashMap / HashSet modules instead.
import { MutableList } from "effect"
// Accumulate work in a hot loop without allocating a new list each step
const buffer = MutableList.make<number>()
for (let i = 0; i < 10_000; i++) {
MutableList.append(buffer, i) // mutates `buffer` in place, returns void
}
console.log(buffer.length) // => 10000
// Drain it all at once into a plain array
const all = MutableList.takeAll(buffer)
console.log(all.length) // => 10000
console.log(buffer.length) // => 0 (the list is now empty)

A MutableRef is the single-cell version — a stable reference whose .current value changes over time. It is ideal for a local counter or flag:

import { MutableRef } from "effect"
const counter = MutableRef.make(0)
MutableRef.increment(counter) // returns the same ref
MutableRef.increment(counter)
MutableRef.update(counter, (n) => n + 10)
console.log(MutableRef.get(counter)) // => 12

The rest of this page is an exhaustive reference for the four mutable modules.


A mutable linked list of array buckets, optimized for high-throughput producer/consumer patterns. Values are appended/prepended at the ends and drained from the head (FIFO). Every mutation updates the same instance and its length.

import { MutableList } from "effect"
const queue = MutableList.make<string>()
MutableList.append(queue, "a")
MutableList.append(queue, "b")
console.log(MutableList.take(queue)) // => "a"

A stateful container exposing head, tail (internal buckets, treat as implementation detail), and a length counter. Parameterized by element type.

import { MutableList } from "effect"
// Use the namespace type in annotations
const drain = (list: MutableList.MutableList<number>): Array<number> =>
MutableList.takeAll(list)
const list = MutableList.make<number>()
MutableList.appendAll(list, [1, 2, 3])
console.log(list.length) // => 3
console.log(drain(list)) // => [1, 2, 3]

A unique symbol returned by take when the list has no value. Compare against it explicitly rather than relying on falsy checks (a stored 0, "", or false is a valid value).

import { MutableList } from "effect"
const list = MutableList.make<string>()
const result = MutableList.take(list)
console.log(result === MutableList.Empty) // => true

The type of the Empty symbol, typeof MutableList.Empty. Used to type the result of take as A | MutableList.Empty so callers handle the empty case type-safely.

import { MutableList } from "effect"
const list = MutableList.make<number>()
MutableList.append(list, 5)
const item: number | MutableList.Empty = MutableList.take(list)
if (item !== MutableList.Empty) {
console.log(item * 2) // => 10
}

Creates a new empty MutableList.

import { MutableList } from "effect"
const list = MutableList.make<number>()
console.log(list.length) // => 0

Adds one element to the tail. Returns void and mutates in place; preserves FIFO order with take.

import { MutableList } from "effect"
const list = MutableList.make<number>()
MutableList.append(list, 1)
MutableList.append(list, 2)
console.log(MutableList.toArray(list)) // => [1, 2]

Adds one element to the head, so it is taken before existing contents (useful for priority work or restoring an item to the front). Returns void.

import { MutableList } from "effect"
const list = MutableList.make<string>()
MutableList.append(list, "normal")
MutableList.prepend(list, "priority")
console.log(MutableList.take(list)) // => "priority"

Prepends every element of an iterable to the head, in order, so the first element of the iterable becomes the new head. Returns void.

import { MutableList } from "effect"
const list = MutableList.make<number>()
MutableList.appendAll(list, [4, 5])
MutableList.prependAll(list, [1, 2, 3])
console.log(MutableList.takeAll(list)) // => [1, 2, 3, 4, 5]

Optimized prependAll for a ReadonlyArray. Pass mutable: true only when you own the array’s lifecycle — the list may then reuse/modify it internally for efficiency. Returns void.

import { MutableList } from "effect"
const list = MutableList.make<number>()
MutableList.append(list, 4)
const items = [1, 2, 3]
MutableList.prependAllUnsafe(list, items) // mutable defaults to false
console.log(items) // => [1, 2, 3] (unchanged)
console.log(MutableList.takeAll(list)) // => [1, 2, 3, 4]

Appends every element of an iterable to the tail, preserving order, and returns the number of elements added.

import { MutableList } from "effect"
const list = MutableList.make<number>()
const added = MutableList.appendAll(list, [1, 2, 3])
console.log(added) // => 3
console.log(MutableList.toArray(list)) // => [1, 2, 3]

Optimized appendAll for a ReadonlyArray, returning the number of elements added. With mutable: true the list may reuse the array; only enable it when you control that array.

import { MutableList } from "effect"
const list = MutableList.make<number>()
const big = Array.from({ length: 1000 }, (_, i) => i)
const added = MutableList.appendAllUnsafe(list, big, true) // very efficient
console.log(added) // => 1000

Empties the list in place, releasing internal memory, and resets length to 0. Returns void. The same instance can be reused afterward.

import { MutableList } from "effect"
const list = MutableList.make<number>()
MutableList.appendAll(list, [1, 2, 3])
MutableList.clear(list)
console.log(list.length) // => 0

Removes all occurrences of a value, in place, using JavaScript strict equality (!==) — not Effect structural equality. Returns void.

import { MutableList } from "effect"
const list = MutableList.make<string>()
MutableList.appendAll(list, ["a", "b", "a", "c", "a"])
MutableList.remove(list, "a")
console.log(MutableList.takeAll(list)) // => ["b", "c"]

Keeps only the elements satisfying the predicate (value, index) => boolean, rebuilding the list in place. Returns void.

import { MutableList } from "effect"
const list = MutableList.make<number>()
MutableList.appendAll(list, [1, 2, 3, 4, 5, 6])
MutableList.filter(list, (n) => n % 2 === 0)
console.log(MutableList.takeAll(list)) // => [2, 4, 6]

Removes and returns the single head element, or the Empty symbol if the list is empty.

import { MutableList } from "effect"
const list = MutableList.make<string>()
MutableList.appendAll(list, ["first", "second"])
console.log(MutableList.take(list)) // => "first"
console.log(MutableList.take(list)) // => "second"
console.log(MutableList.take(list) === MutableList.Empty) // => true

Removes up to n elements from the head and returns them as an array (fewer if the list is shorter). Includes zero-copy fast paths.

import { MutableList } from "effect"
const list = MutableList.make<number>()
MutableList.appendAll(list, [1, 2, 3, 4, 5])
console.log(MutableList.takeN(list, 3)) // => [1, 2, 3]
console.log(MutableList.takeN(list, 10)) // => [4, 5]
console.log(MutableList.takeN(list, 1)) // => []

Drops up to n elements from the head without returning them. If n <= 0 or the list is empty it does nothing; if n >= length the list is cleared. Returns void.

import { MutableList } from "effect"
const list = MutableList.make<number>()
MutableList.appendAll(list, [1, 2, 3, 4, 5])
MutableList.takeNVoid(list, 2)
console.log(MutableList.toArray(list)) // => [3, 4, 5]

Removes and returns every element as an array, leaving the list empty. Equivalent to takeN(list, list.length).

import { MutableList } from "effect"
const list = MutableList.make<string>()
MutableList.appendAll(list, ["a", "b", "c"])
console.log(MutableList.takeAll(list)) // => ["a", "b", "c"]
console.log(list.length) // => 0

Copies all current elements into a new array without modifying the list — a snapshot that leaves the list intact (contrast with takeAll).

import { MutableList } from "effect"
const list = MutableList.make<number>()
MutableList.appendAll(list, [1, 2, 3])
console.log(MutableList.toArray(list)) // => [1, 2, 3]
console.log(list.length) // => 3 (still there)

Copies up to n elements from the head into a new array without modifying the list. Use it to inspect a bounded prefix.

import { MutableList } from "effect"
const list = MutableList.make<number>()
MutableList.appendAll(list, [10, 20, 30, 40])
console.log(MutableList.toArrayN(list, 2)) // => [10, 20]
console.log(list.length) // => 4 (unchanged)

An in-place key-value map. It pairs a native Map for ordinary JavaScript keys with hash buckets for keys implementing Effect Equal / Hash, so structural and referential keys can coexist. Mutating operations change the same instance and return it for piping.

import { MutableHashMap } from "effect"
const map = MutableHashMap.empty<string, number>()
MutableHashMap.set(map, "count", 1)
MutableHashMap.set(map, "count", 2) // overwrites
console.log(MutableHashMap.get(map, "count")) // => Option.some(2)

Keys are matched with the same Equal / Hash rules as the immutable HashMap, so values implementing Equal are compared structurally — mutating a structural key after insertion can break future lookups if its hash changes.

A mutable map of K to V, iterable as [key, value] pairs, Pipeable, and Inspectable. Reports size in O(1).

import { MutableHashMap } from "effect"
const map: MutableHashMap.MutableHashMap<string, number> = MutableHashMap.make(
["a", 1],
["b", 2]
)
console.log(Array.from(map)) // => [["a", 1], ["b", 2]]

Type guard narrowing an unknown value to MutableHashMap. Checks the runtime marker only (not key/value types).

import { MutableHashMap } from "effect"
console.log(MutableHashMap.isMutableHashMap(MutableHashMap.empty())) // => true
console.log(MutableHashMap.isMutableHashMap(new Map())) // => false

Creates a fresh empty map. Each call returns a new instance.

import { MutableHashMap } from "effect"
const map = MutableHashMap.empty<string, number>()
console.log(MutableHashMap.size(map)) // => 0

Creates a map from explicit [key, value] entries known at the call site.

import { MutableHashMap } from "effect"
const map = MutableHashMap.make(["a", 1], ["b", 2], ["c", 3])
console.log(MutableHashMap.size(map)) // => 3

Creates a map from any iterable of [key, value] pairs (arrays, native Map, generators, …).

import { MutableHashMap } from "effect"
const map = MutableHashMap.fromIterable(new Map([["x", 10], ["y", 20]]))
console.log(MutableHashMap.get(map, "x")) // => Option.some(10)

Looks up a key, returning Option.some(value) if present or Option.none() otherwise. Pipeable.

import { MutableHashMap } from "effect"
const map = MutableHashMap.make(["a", 1])
console.log(MutableHashMap.get(map, "a")) // => Option.some(1)
console.log(MutableHashMap.get(map, "z")) // => Option.none()

Returns an iterable over the map’s keys.

import { MutableHashMap } from "effect"
const map = MutableHashMap.make(["a", 1], ["b", 2])
console.log(Array.from(MutableHashMap.keys(map))) // => ["a", "b"]

Returns an iterable over the map’s values.

import { MutableHashMap } from "effect"
const map = MutableHashMap.make(["a", 1], ["b", 2])
console.log(Array.from(MutableHashMap.values(map))) // => [1, 2]

Tests whether a key is present, without reading its value. Pipeable.

import { MutableHashMap } from "effect"
const map = MutableHashMap.make(["a", 1])
console.log(MutableHashMap.has(map, "a")) // => true
console.log(MutableHashMap.has(map, "z")) // => false

Inserts a new entry or replaces an existing one, in place. Returns the map. Pipeable.

import { MutableHashMap } from "effect"
const map = MutableHashMap.empty<string, number>()
MutableHashMap.set(map, "a", 1)
MutableHashMap.set(map, "a", 99) // replaces
console.log(MutableHashMap.get(map, "a")) // => Option.some(99)

Applies a function to the value of an existing key, in place. If the key is absent the map is unchanged. Returns the map. Pipeable.

import { MutableHashMap } from "effect"
const map = MutableHashMap.make(["count", 5])
MutableHashMap.modify(map, "count", (n) => n + 1)
console.log(MutableHashMap.get(map, "count")) // => Option.some(6)
MutableHashMap.modify(map, "missing", (n) => n + 1) // no effect
console.log(MutableHashMap.has(map, "missing")) // => false

Updates, inserts, or removes a key from a function Option<V> => Option<V> of its current optional value. Returning Option.none() removes the key. Returns the map. Pipeable.

import { MutableHashMap, Option } from "effect"
const map = MutableHashMap.make(["count", 5])
// Insert when absent
MutableHashMap.modifyAt(map, "new", (o) =>
Option.isNone(o) ? Option.some(42) : o
)
console.log(MutableHashMap.get(map, "new")) // => Option.some(42)
// Remove by returning None
MutableHashMap.modifyAt(map, "count", () => Option.none())
console.log(MutableHashMap.has(map, "count")) // => false

Deletes one key in place; a no-op if the key is absent. Returns the map. Pipeable.

import { MutableHashMap } from "effect"
const map = MutableHashMap.make(["a", 1], ["b", 2])
MutableHashMap.remove(map, "a")
console.log(MutableHashMap.has(map, "a")) // => false
console.log(MutableHashMap.size(map)) // => 1

Removes all entries in place, leaving the same map instance empty. Returns the map.

import { MutableHashMap } from "effect"
const map = MutableHashMap.make(["a", 1], ["b", 2])
MutableHashMap.clear(map)
console.log(MutableHashMap.size(map)) // => 0

Returns the number of entries, in O(1).

import { MutableHashMap } from "effect"
const map = MutableHashMap.make(["a", 1], ["b", 2])
console.log(MutableHashMap.size(map)) // => 2

Returns true when the map has no entries.

import { MutableHashMap } from "effect"
console.log(MutableHashMap.isEmpty(MutableHashMap.empty())) // => true
console.log(MutableHashMap.isEmpty(MutableHashMap.make(["a", 1]))) // => false

Runs a callback for each entry. The callback receives (value, key) — value first — matching Map.prototype.forEach. Pipeable.

import { MutableHashMap } from "effect"
const map = MutableHashMap.make(["a", 1], ["b", 2])
MutableHashMap.forEach(map, (value, key) => {
console.log(`${key} = ${value}`)
})
// => a = 1
// => b = 2

An in-place set of unique values, built on MutableHashMap (each value is stored as a key). Uniqueness follows the same Equal / Hash rules, so structural values are de-duplicated structurally. Mutating operations return the same set instance for piping.

import { MutableHashSet } from "effect"
const set = MutableHashSet.make("alice", "bob", "alice")
console.log(MutableHashSet.size(set)) // => 2

A mutable collection of unique values of type V, iterable, Pipeable, and Inspectable.

import { MutableHashSet } from "effect"
const set: MutableHashSet.MutableHashSet<number> = MutableHashSet.make(1, 2, 3)
console.log(Array.from(set)) // => [1, 2, 3]

Type guard narrowing an unknown value to MutableHashSet. Native Set values do not satisfy it.

import { MutableHashSet } from "effect"
console.log(MutableHashSet.isMutableHashSet(MutableHashSet.empty())) // => true
console.log(MutableHashSet.isMutableHashSet(new Set())) // => false

Creates a fresh empty set, backed by an empty MutableHashMap.

import { MutableHashSet } from "effect"
const set = MutableHashSet.empty<string>()
console.log(MutableHashSet.size(set)) // => 0

Creates a set from explicit values; duplicates are dropped.

import { MutableHashSet } from "effect"
const set = MutableHashSet.make("a", "b", "a", "c")
console.log(MutableHashSet.size(set)) // => 3
console.log(Array.from(set)) // => ["a", "b", "c"]

Creates a set from any iterable of values, dropping duplicates.

import { MutableHashSet } from "effect"
const set = MutableHashSet.fromIterable("hello")
console.log(Array.from(set)) // => ["h", "e", "l", "o"]

Inserts a value in place. Adding an existing value is a no-op. Returns the set. Pipeable.

import { MutableHashSet } from "effect"
const set = MutableHashSet.empty<string>()
MutableHashSet.add(set, "apple")
MutableHashSet.add(set, "apple") // duplicate, ignored
console.log(MutableHashSet.size(set)) // => 1

Tests membership, using the underlying map’s hashing/equality rules. Pipeable.

import { MutableHashSet } from "effect"
const set = MutableHashSet.make("apple", "banana")
console.log(MutableHashSet.has(set, "apple")) // => true
console.log(MutableHashSet.has(set, "grape")) // => false

Deletes a value in place; a no-op if absent. Returns the set. Pipeable.

import { MutableHashSet } from "effect"
const set = MutableHashSet.make("a", "b", "c")
MutableHashSet.remove(set, "b")
console.log(MutableHashSet.has(set, "b")) // => false
console.log(MutableHashSet.size(set)) // => 2

Returns the number of unique values.

import { MutableHashSet } from "effect"
const set = MutableHashSet.make(1, 2, 3, 2, 1)
console.log(MutableHashSet.size(set)) // => 3

Removes all values in place, leaving the same set instance empty. Returns the set.

import { MutableHashSet } from "effect"
const set = MutableHashSet.make("a", "b", "c")
MutableHashSet.clear(set)
console.log(MutableHashSet.size(set)) // => 0

A single mutable cell holding one current value, exposed through .current. Reads and writes are synchronous. getAnd* helpers return the previous value; *AndGet helpers return the new value; set/update/increment/toggle return the same reference.

MutableRef is not fiber-safe and carries no transactional or effectful guarantees. For state that must participate in Effect workflows, interruption, or fiber coordination, use Ref instead.

import { MutableRef } from "effect"
const ref = MutableRef.make(42)
console.log(ref.current) // => 42
console.log(MutableRef.get(ref)) // => 42

A stable reference whose .current field of type T may change over time. Pipeable and Inspectable; read or write .current directly, or use the helpers below.

import { MutableRef } from "effect"
const ref: MutableRef.MutableRef<number> = MutableRef.make(1)
ref.current = 100 // direct mutation works
console.log(MutableRef.get(ref)) // => 100

Creates a new MutableRef initialized with a value.

import { MutableRef } from "effect"
const status = MutableRef.make("idle")
console.log(MutableRef.get(status)) // => "idle"

Reads the current value without mutating the reference.

import { MutableRef } from "effect"
const ref = MutableRef.make("hello")
console.log(MutableRef.get(ref)) // => "hello"

Replaces the current value in place and returns the same reference (not a copy). Pipeable.

import { MutableRef } from "effect"
const ref = MutableRef.make("a")
const same = MutableRef.set(ref, "b")
console.log(same === ref) // => true
console.log(MutableRef.get(ref)) // => "b"

Replaces the current value and returns the new value. Pipeable.

import { MutableRef } from "effect"
const ref = MutableRef.make("old")
console.log(MutableRef.setAndGet(ref, "new")) // => "new"

Replaces the current value and returns the previous value. Pipeable.

import { MutableRef } from "effect"
const ref = MutableRef.make("old")
console.log(MutableRef.getAndSet(ref, "new")) // => "old"
console.log(MutableRef.get(ref)) // => "new"

Applies a function to the current value, storing the result in place, and returns the same reference. Pipeable.

import { MutableRef } from "effect"
const ref = MutableRef.make(5)
MutableRef.update(ref, (n) => n * 2)
console.log(MutableRef.get(ref)) // => 10

Applies a function to the current value and returns the new value. Pipeable.

import { MutableRef } from "effect"
const ref = MutableRef.make(5)
console.log(MutableRef.updateAndGet(ref, (n) => n + 1)) // => 6

Applies a function to the current value and returns the previous value. Pipeable.

import { MutableRef } from "effect"
const ref = MutableRef.make(5)
console.log(MutableRef.getAndUpdate(ref, (n) => n + 1)) // => 5
console.log(MutableRef.get(ref)) // => 6

Sets newValue only if the current value equals oldValue, comparing with Effect’s Equal.equals (structural, not just reference equality). Returns true if it updated, false otherwise. Pipeable.

import { MutableRef } from "effect"
const ref = MutableRef.make("initial")
console.log(MutableRef.compareAndSet(ref, "initial", "updated")) // => true
console.log(MutableRef.compareAndSet(ref, "initial", "again")) // => false
console.log(MutableRef.get(ref)) // => "updated"

Flips a boolean MutableRef between true and false, returning the same reference.

import { MutableRef } from "effect"
const flag = MutableRef.make(false)
MutableRef.toggle(flag)
console.log(MutableRef.get(flag)) // => true

Adds 1 to a numeric MutableRef in place and returns the same reference.

import { MutableRef } from "effect"
const counter = MutableRef.make(5)
MutableRef.increment(counter)
console.log(MutableRef.get(counter)) // => 6

Adds 1 and returns the new value (pre-increment, like ++i).

import { MutableRef } from "effect"
const counter = MutableRef.make(5)
console.log(MutableRef.incrementAndGet(counter)) // => 6

Adds 1 and returns the previous value (post-increment, like i++). Handy for ID generation.

import { MutableRef } from "effect"
const ids = MutableRef.make(0)
console.log(MutableRef.getAndIncrement(ids)) // => 0
console.log(MutableRef.getAndIncrement(ids)) // => 1

Subtracts 1 from a numeric MutableRef in place and returns the same reference.

import { MutableRef } from "effect"
const counter = MutableRef.make(5)
MutableRef.decrement(counter)
console.log(MutableRef.get(counter)) // => 4

Subtracts 1 and returns the new value.

import { MutableRef } from "effect"
const counter = MutableRef.make(5)
console.log(MutableRef.decrementAndGet(counter)) // => 4

Subtracts 1 and returns the previous value.

import { MutableRef } from "effect"
const counter = MutableRef.make(5)
console.log(MutableRef.getAndDecrement(counter)) // => 5
console.log(MutableRef.get(counter)) // => 4