LangGraph Tutorial (TypeScript): implementing retry logic for beginners

By Cyprian AaronsUpdated 2026-04-22
langgraphimplementing-retry-logic-for-beginnerstypescript

This tutorial shows how to add retry logic to a LangGraph workflow in TypeScript using a real node-level pattern. You’ll build a graph that retries transient failures a fixed number of times before failing cleanly, which is the right move when you’re calling flaky APIs, model endpoints, or internal services.

What You'll Need

  • Node.js 18+
  • TypeScript 5+
  • @langchain/langgraph
  • @langchain/core
  • A package manager like npm, pnpm, or yarn
  • A terminal and a TypeScript project already set up
  • Optional: an API key if you want to swap the mock step for a real LLM call

Install the packages:

npm install @langchain/langgraph @langchain/core
npm install -D typescript tsx @types/node

Step-by-Step

  1. Start by defining the state your graph will carry. For retry logic, you need fields for the current attempt count, the maximum number of retries, the result, and any error message.
import { Annotation, StateGraph, END } from "@langchain/langgraph";

const GraphState = Annotation.Root({
  attempt: Annotation<number>({
    reducer: (_, update) => update,
    default: () => 0,
  }),
  maxRetries: Annotation<number>({
    reducer: (_, update) => update,
    default: () => 3,
  }),
  result: Annotation<string | null>({
    reducer: (_, update) => update,
    default: () => null,
  }),
  error: Annotation<string | null>({
    reducer: (_, update) => update,
    default: () => null,
  }),
});
  1. Next, create a task node that sometimes fails. In production this would be an API call or model invocation; here we simulate a transient failure so you can see the retry path clearly.
const unstableTask = async (state: typeof GraphState.State) => {
  const nextAttempt = state.attempt + 1;

  if (nextAttempt < 3) {
    throw new Error(`Transient failure on attempt ${nextAttempt}`);
  }

  return {
    attempt: nextAttempt,
    result: `Success on attempt ${nextAttempt}`,
    error: null,
  };
};
  1. Add a retry handler node that increments the attempt counter and stores the error. This keeps the retry decision inside the graph instead of hiding it in application code.
const handleFailure = async (state: typeof GraphState.State) => {
  return {
    attempt: state.attempt + 1,
    error: `Retrying after failure at attempt ${state.attempt + 1}`,
  };
};
  1. Now wire the graph together with conditional routing. If the task succeeds, end the workflow; if it fails and you still have retries left, route back through the retry handler and then try again.
const shouldRetry = (state: typeof GraphState.State) => {
  if (state.error === null) return END;
  return state.attempt < state.maxRetries ? "handleFailure" : END;
};

const builder = new StateGraph(GraphState)
  .addNode("unstableTask", unstableTask)
  .addNode("handleFailure", handleFailure)
  .addEdge("__start__", "unstableTask")
  .addConditionalEdges("unstableTask", shouldRetry, {
    handleFailure: "handleFailure",
    [END]: END,
  })
  .addEdge("handleFailure", "unstableTask");

export const graph = builder.compile();
  1. Run the graph and inspect the final state. The important part is that retries are explicit in the graph state, so you can log them, persist them, or branch on them later.
async function main() {
  const output = await graph.invoke({
    attempt: 0,
    maxRetries: 5,
    result: null,
    error: null,
  });

  console.log(output);
}

main().catch((err) => {
  console.error(err);
});

Testing It

Run the file with tsx or compile it with tsc first. You should see the workflow fail twice and then succeed on the third attempt because unstableTask throws until attempt >= 3.

If you want to test the failure path, lower maxRetries to 2. The graph should stop without producing a success result because it ran out of retries.

For real systems, replace unstableTask with an LLM call or HTTP request and keep the same structure. That gives you deterministic control over how many times you’ll retry before surfacing an error to your app.

Next Steps

  • Add exponential backoff between attempts using a delay node or external scheduler
  • Persist retry metadata in Redis or Postgres so retries survive process restarts
  • Combine this pattern with structured outputs and validation so only bad responses get retried

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides