Tutorials Logic, IN info@tutorialslogic.com

LangGraph Reducers and State Updates: Merge Rules, Message History, and Safe Mutation

LangGraph Reducers and State Updates

Most beginner graphs work because every node updates a different field. Real workflows stop being that polite. Multiple steps may append to messages, collect findings, add search results, or build a running log. That is where reducers matter.

A reducer tells LangGraph how to combine a new update with existing state for a specific field. Without that merge rule, an append-like field can be accidentally replaced, which turns debugging into guesswork.

This page is about disciplined mutation: knowing exactly which fields replace, which append, and which should be summarized before they grow forever.

What a Reducer Actually Controls

A reducer is not a general business-rule engine. It is a merge rule for one field. When two updates touch that field across steps, the reducer defines whether values replace, append, concatenate, or otherwise combine.

This is especially common for message histories, accumulated documents, logs, and lists of extracted facts.

  • Replacement fields: current status, final answer, approval flag
  • Append fields: messages, audit events, collected sources
  • Custom merge fields: deduplicated items, score aggregates, counters with bounds

Default Replacement Versus Append Behavior

If you do nothing, the intuitive assumption is usually replacement. That is fine for many current-value fields. It is dangerous for conversation memory or audit trails because each node can erase what came before.

Explicit reducers communicate intent. A teammate reading the state definition should know immediately whether a field is a timeline or a single current value.

  • Use replacement for the latest answer or status.
  • Use append-style reducers for conversational or historical fields.
  • Test merge behavior early instead of discovering it during long runs.

Messages Need Retention Rules Too

Appending forever is not a strategy. Message-heavy graphs become expensive, slow, and noisy if you never trim or summarize state. Reducers solve merge correctness, not memory hygiene.

As soon as a workflow can run across many turns or many tool calls, decide what stays verbatim, what gets summarized, and what can be dropped after checkpointed persistence.

  • Keep the newest turns verbatim.
  • Summarize stale context into a compact field.
  • Store raw artifacts outside state when long-term retrieval matters.

Custom Merge Patterns for Production Graphs

Production systems often need more than plain append. You may want a reducer that deduplicates documents by ID, keeps only the top N findings, or merges tool results keyed by tool name.

The main rule is predictability. If a reducer hides surprising logic, the graph becomes harder to reason about than if you had used explicit nodes to maintain those structures.

  • Prefer simple merge rules.
  • Move complicated transformations into dedicated nodes.
  • Unit test reducers with multiple sequential updates.

Execution Walkthrough: Why Merge Rules Change Outcomes

Imagine two nodes both write to `messages`. If the field replaces, the second node wipes out the first node output. If the field appends, the state preserves the conversation trail. Same nodes, different merge rule, very different graph behavior.

That is why reducers are part of architecture, not syntactic trivia.

  • Initial state: messages=[]
  • node A returns one message
  • node B returns one message
  • replacement => only node B remains
  • append reducer => both messages remain in order

Reducers Define Collaboration Rules

Reducers decide how multiple updates to the same state field combine. That makes them collaboration rules for nodes. A message history may append. A counter may add. A status field may overwrite. A risk list may merge unique items. Choosing the wrong reducer can silently corrupt workflow meaning.

Default overwriting is often safest for fields with one owner. If only the classifier should set `category`, overwriting is clear. If many nodes can append observations, a reducer may be appropriate. Do not use append reducers because they feel convenient; use them because multiple writers are intentional.

Reducers should be tested with small examples. Give them old state, two updates, and expected merged state. This catches duplicates, order sensitivity, lost fields, and accidental mutation.

State updates should be minimal. A node should return only the fields it owns or intentionally changes. Returning the whole state from every node makes it easy to overwrite unrelated fields and hard to trace responsibility.

  • Use reducers only for intentionally shared fields.
  • Prefer overwrite for single-owner fields.
  • Test reducer behavior directly.
  • Return minimal node updates.
  • Document which node owns each state field.

State Mutation Discipline

LangGraph state should be treated as an immutable contract between nodes. Nodes receive state and return updates. Mutating nested objects in place can create confusing behavior, especially when checkpoints, retries, or parallel branches are involved.

Design state fields by lifecycle. Some fields are input facts, some are working decisions, some are accumulated evidence, some are operational metadata, and some are final output. Mixing all of these into one dictionary field removes the benefits of typed state.

Parallelism makes update discipline even more important. If multiple branches write to the same field, define a reducer and decide how conflicts resolve. If branches should not conflict, give them separate fields and merge later in a dedicated node.

Finally, state design is product design. The fields you choose determine what can be explained, resumed, tested, and audited. A graph with clear state is much easier to make reliable than a graph that hides meaning inside message history.

  • Avoid in-place mutation of nested state.
  • Group fields by lifecycle and ownership.
  • Use dedicated merge nodes for complex branch results.
  • Keep final output separate from working notes.
  • Review state schema before adding new nodes.

State Schema Review Exercise

Print the state schema and mark each field with its owner, lifecycle, reducer, and privacy level. If no node clearly owns a field, the field is likely to become confusing. If many nodes own it, it probably needs a reducer or a merge node.

Then review one complete run and identify every state update. Each update should be small, intentional, and traceable. Large updates that rewrite unrelated fields make debugging harder and increase the chance of accidental data loss.

Finally, write reducer tests for every merged field. Reducer bugs are subtle because the graph may still run while producing incorrect accumulated state.

  • Assign owner, lifecycle, reducer, and privacy level.
  • Keep node updates minimal.
  • Use merge nodes for complex parallel results.
  • Write direct reducer tests.

Beginner Example: Append Message History

This is the canonical reducer pattern: keep every message instead of replacing the list on each node update.

Beginner Example: Append Message History
import operator
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END

class ChatState(TypedDict):
    messages: Annotated[list[str], operator.add]

def user_turn(state: ChatState) -> dict:
    return {"messages": ["User: Where is my order?"]}

def assistant_turn(state: ChatState) -> dict:
    return {"messages": ["Assistant: I will look it up."]}
  • The `Annotated` field tells LangGraph to combine list updates with `operator.add`.
  • Each node returns only the new slice it wants to append.
  • This pattern is safer than rebuilding the full list manually in each node.

Intermediate Example: Replace Current Status, Append Audit Log

Many graphs need mixed behavior: one field should always show the newest status while another preserves the history.

Intermediate Example: Replace Current Status, Append Audit Log
import operator
from typing import Annotated
from typing_extensions import TypedDict

class ReviewState(TypedDict):
    status: str
    audit_log: Annotated[list[str], operator.add]

def validate_invoice(state: ReviewState) -> dict:
    return {
        "status": "validated",
        "audit_log": ["validator: invoice fields passed"],
    }

def send_for_approval(state: ReviewState) -> dict:
    return {
        "status": "waiting_for_approval",
        "audit_log": ["workflow: sent to approver queue"],
    }
  • `status` replaces because only the latest state matters there.
  • `audit_log` appends because the history is operationally useful.
  • Mixed merge behavior is normal in real workflows.

Advanced Example: Keep Only Unique Documents

A custom reducer can deduplicate retrieved items when multiple search nodes may surface the same source.

Advanced Example: Keep Only Unique Documents
from typing import Annotated
from typing_extensions import TypedDict

def merge_unique_docs(existing: list[dict], incoming: list[dict]) -> list[dict]:
    by_id = {doc["id"]: doc for doc in existing}
    for doc in incoming:
        by_id[doc["id"]] = doc
    return list(by_id.values())

class RetrievalState(TypedDict):
    docs: Annotated[list[dict], merge_unique_docs]
  • Custom reducers are best when the rule is stable and easy to explain.
  • This avoids duplicated retrieval context across branches.
  • Keep the reducer simple enough that a unit test can describe its full behavior clearly.
Key Takeaways
  • Choose merge behavior field by field, not globally.
  • Append for history, replace for current values.
  • Add retention and summarization rules for long-lived state.
  • Test reducers with sequential updates before relying on them in loops.
Common Mistakes to Avoid
Appending every field because preserving history feels safe.
Using reducers to hide complicated business logic better expressed in nodes.
Assuming message history can grow without cost or noise.

Practice Tasks

  • Define one state with both replacement and append fields.
  • Write a custom reducer that deduplicates documents by ID.
  • Create a test that proves two node updates merge the way you expect.

Frequently Asked Questions

No. Messages are the common example, but reducers are useful for logs, sources, findings, and any field that should merge instead of replace.

You can, but it becomes brittle and easy to overwrite accidentally as the graph grows.

Silent state corruption. The graph may run successfully while carrying incomplete or duplicated context.

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.