Module 1 ended with a promise: build the complete mental map this course runs on, one layer at a time, before touching real code. That’s exactly this module’s job. Every concept you meet from here forward — reducers, Command, interrupts, subgraphs — is going to slot into one of the layers below. Get this map genuinely clear now, and nothing later in this course will feel like a disconnected, new idea.
The five layers, named precisely
flowchart TD
A[State] --> B[Nodes]
B --> C[Edges]
C --> D[Routing]
D --> E[Execution]
E --> F[Updated State]
F -.feeds back into.-> A
Let’s walk through this slowly, because each layer answers one specific, honest question, and it’s worth knowing exactly which question before you meet the real syntax for it.
State answers: what does this workflow actually need to remember, right now? Not the model’s memory, not a vague notion of context — a real, concrete, inspectable structure. A customer ID. A diagnosis. Whether an action has been approved yet.
Nodes answer: what actual work happens? Recall from Module 1 that a node isn’t synonymous with “an LLM call” — it’s any real unit of work: calling a model, running a tool, querying a database, or just transforming data with plain Python.
Edges answer: what comes next, after this node finishes? The simplest possible answer is “always this other node.” A more useful answer, covered properly soon, depends on what the state actually contains.
Routing answers the harder version of that same question: which of several possible next steps should actually run, based on what’s currently in state? This is where a workflow stops being a straight line and starts being a genuine decision tree.
Execution is simply the graph actually running — each node executing, state updating, routing deciding what’s next, over and over, until there’s nowhere left to go.
Updated State, feeding back into the top of the diagram, is worth genuinely sitting with: state isn’t a one-time input. It’s continuously read, updated, and re-read, node after node, for the entire run.
Seeing all five layers in one small, complete graph
Let’s ground this immediately in real code — small enough to hold every layer in your head at once.
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
message: str
is_loud: bool
def check_loudness(state: State) -> dict:
return {"is_loud": len(state["message"]) > 20}
def shout(state: State) -> dict:
return {"message": state["message"].upper() + "!!!"}
def whisper(state: State) -> dict:
return {"message": state["message"].lower() + "..."}
def route(state: State) -> str:
return "shout" if state["is_loud"] else "whisper"
builder = StateGraph(State)
builder.add_node("check_loudness", check_loudness)
builder.add_node("shout", shout)
builder.add_node("whisper", whisper)
builder.add_edge(START, "check_loudness")
builder.add_conditional_edges("check_loudness", route, ["shout", "whisper"])
builder.add_edge("shout", END)
builder.add_edge("whisper", END)
graph = builder.compile()
print(graph.invoke({"message": "This is a genuinely long test message", "is_loud": False}))
print(graph.invoke({"message": "hi", "is_loud": False}))
Now map this directly back onto the five layers, piece by piece:
- State is
class State(TypedDict)— a real, typed structure, not a loose dictionary you’re trusting yourself to use consistently. - Nodes are
check_loudness,shout,whisper— three separate, real units of work. - Edges are
add_edge(START, "check_loudness"), and the two edges fromshout/whispertoEND— fixed, unconditional transitions. - Routing is
route(), paired withadd_conditional_edges— a real, honest decision, made by actually readingstate["is_loud"]. - Execution is
graph.invoke(...)— the whole thing actually running, start to finish.
You haven’t learned any new syntax you’ll need to memorize separately later — every piece of this small graph is a real, working instance of one of the five layers.
Why the “State” layer being typed genuinely matters, not just stylistically
It’s worth pausing on class State(TypedDict) specifically, because it’s easy to treat this as a stylistic preference rather than a genuine, practical safeguard. If check_loudness returned a field named loud instead of is_loud — a simple, easy typo — a real, current type checker catches that mismatch at the moment you write the code, not later, buried inside a live execution. This is a genuinely well-known, practical piece of engineering wisdom in the LangGraph community, often phrased bluntly: a typed state surfaces a contract violation immediately, at definition time — not at 3 AM, in production, when an on-call engineer is trying to figure out why a node silently received a field it never expected.
What graph.invoke() is actually doing underneath
This is worth being completely explicit about now, early, because letting .invoke() remain a black box is exactly what makes a framework start to feel like magic — and this entire course is committed to never letting that happen.
flowchart TD
A[Load current state] --> B[Find the current node]
B --> C[Execute that node]
C --> D[Merge the node's returned update into state]
D --> E[Determine the next edge, using routing if conditional]
E --> F{More nodes to run?}
F -->|Yes| B
F -->|No, reached END| G[Return final state]
Every single time you call .invoke() throughout this entire course, this is genuinely, mechanically what’s happening. There’s no hidden reasoning, no separate intelligence deciding things behind the scenes beyond what you’ve explicitly wired into nodes and routing functions yourself. When something in a graph behaves unexpectedly — and it will, at some point — this diagram is the actual, honest place to start debugging: which node ran, what state went in, what came out, and which edge got taken next.
Common mistakes worth avoiding
Treating graph.invoke() as a black box the first time something behaves unexpectedly. Recall this module’s own internals diagram — every “why did this happen” question has a real, mechanical answer: which node ran, what state went in, what came out, which edge got taken. Reaching for guesswork before checking these four things directly wastes real debugging time.
Confusing “routing” with “edges” as if they’re the same thing. Recall the five-layer map — Edges are the possible connections; Routing is the decision about which one actually gets taken. A workflow with only fixed edges genuinely has no routing layer doing any real work at all, and that’s a completely valid, simple graph, not an incomplete one.
What you should take away from this module
- State, Nodes, Edges, Routing, Execution are the five real layers every LangGraph concept in this course belongs to — new syntax will always map onto one of these, not introduce a sixth.
- A typed state isn’t a stylistic choice — it catches real, honest mistakes (a mismatched field name between two nodes) at definition time, rather than mysteriously, silently, in a live run.
graph.invoke()is not magic: load state, find the node, run it, merge its update, pick the next edge, repeat untilEND. Every debugging question you’ll ever ask about a misbehaving graph traces back to this exact loop.
Where this goes next
The next module goes deep on the first, and arguably most important, layer: State itself — schemas, multiple fields, optional data, and watching exactly what a real state dictionary looks like before and after a node runs.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed