LangGraph Loops and Cycles: Retry Logic and Iterative Workflows

This is where LangGraph pulls ahead of simple chains. A cycle sends the graph back to an earlier node, so it can retry, refine, or repeat until a goal is met.

The idea

        +--------------+
        v              |
START -> increment -> check --(done)--> END
                       |
                    (again)

A loop is just a conditional edge (Chapter 6) that points backward.

Example 1: A simple counter loop

from typing import TypedDict
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    count: int

def increment(state: State):
    return {"count": state["count"] + 1}

def check(state: State):
    if state["count"] < 3:
        return "again"
    return "done"

builder = StateGraph(State)
builder.add_node("increment", increment)

builder.add_edge(START, "increment")
builder.add_conditional_edges(
    "increment",
    check,
    {"again": "increment", "done": END},
)

graph = builder.compile()

for step in graph.stream({"count": 0}, stream_mode="updates"):
    print(step)

Output:

{'increment': {'count': 1}}
{'increment': {'count': 2}}
{'increment': {'count': 3}}

How it works:

  1. increment runs and raises the count
  2. check reads the count. Below 3, it returns “again”, which loops back to increment
  3. At 3, it returns “done”, and the graph ends

Example 2: Retry logic

A realistic use: try a task, and retry if it fails, with a maximum number of attempts. We will simulate a task that succeeds on the third try.

import operator
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    attempts: int
    success: bool
    log: Annotated[list[str], operator.add]

def try_task(state: State):
    attempt = state["attempts"] + 1
    success = attempt >= 3          # pretend it works on attempt 3
    result = "success" if success else "failed"
    return {
        "attempts": attempt,
        "success": success,
        "log": [f"Attempt {attempt}: {result}"],
    }

def check(state: State):
    if state["success"]:
        return "done"
    if state["attempts"] >= 5:
        return "give_up"
    return "retry"

builder = StateGraph(State)
builder.add_node("try_task", try_task)

builder.add_edge(START, "try_task")
builder.add_conditional_edges(
    "try_task",
    check,
    {"retry": "try_task", "done": END, "give_up": END},
)

graph = builder.compile()

result = graph.invoke({"attempts": 0, "success": False, "log": []})
print(result)

Output:

{'attempts': 3, 'success': True, 'log': ['Attempt 1: failed', 'Attempt 2: failed', 'Attempt 3: success']}

What to notice:

  • The log uses the operator.add reducer from Chapter 5, so each attempt adds to the history
  • The router has three outcomes: retry, done, and give up
  • The attempts >= 5 check is a safety exit. Every loop needs one.

The recursion limit: your safety net

What if your loop never ends? LangGraph stops it after 25 steps by default and raises an error. You can set your own limit:

from langgraph.errors import GraphRecursionError

try:
    graph.invoke(
        {"attempts": 0, "success": False, "log": []},
        {"recursion_limit": 2},
    )
except GraphRecursionError:
    print("Loop stopped: too many steps")

Output:

Loop stopped: too many steps

Each node run counts as one step, and here the limit of 2 is reached before the task succeeds. Treat the limit as a backup, not your main plan. Your own exit condition (like attempts >= 5) should stop the loop first.

Where loops are used in real AI apps

  • Retry: call an API until it works
  • Self-correction: write an answer, review it, and rewrite if it is weak
  • Agent loop: think, use a tool, think again (Chapter 11)
  • Human feedback: revise a draft until it is approved

Common mistakes

  • No exit condition: the loop runs until the recursion limit crashes it
  • Exit condition never becomes true: for example, forgetting to update the counter inside the node
  • Overwriting the log: without a reducer, each pass erases the history
  • Setting the recursion limit too low: a healthy workflow gets cut off

Summary

  • A cycle is a conditional edge that routes back to an earlier node
  • Always include an exit condition, ideally with a maximum attempts check
  • Reducers let you keep a history across loop passes
  • recursion_limit protects you from infinite loops, and raises GraphRecursionError

Practice

  1. Change Example 2 so the task succeeds on attempt 6, and confirm the graph gives up after 5 attempts
  2. Add a delay field that doubles on each retry (1, 2, 4 and so on) to simulate backoff
  3. Build a loop that keeps adding numbers to a list until the total passes 50

If you liked the tutorial, spread the word and share the link and our website, Studyopedia, with others.


For Videos, Join Our YouTube Channel: Join Now


Read More:

LangGraph Conditional Edges: Route Based on State
Add an LLM Node in LangGraph: Build Your First Chatbot
Studyopedia Editorial Staff
contact@studyopedia.com

We work to create programming tutorials for all.

No Comments

Post A Comment