Skip to content
masterclassBuddyTech Buzz5 fresh blogs · 5:00 AM IST

LangGraph blueprint · Lending / Fintech

Loan underwriting with durable execution

A LangGraph StateGraph takes a loan application through document intake, bureau check, income verification and risk scoring, with a Postgres checkpointer so a crashed run resumes from its last checkpoint and an underwriter decides edge cases through interrupt(). It must never repeat a side effect on resume, such as a second bureau pull or a second booking, and must never issue a decision without a recorded reason and reviewer.

Steps
8
Subjective questions
33
Objective questions
33

The problem and the outcome

Problem: Underwriting pipelines call slow external services and wait hours or days for human review. A worker crash or deploy in the middle often means restarting the application from scratch, repeating bureau inquiries, or losing track of who approved what.

Outcome: Each application is one durable thread that survives failures and long human pauses and picks up exactly where it stopped. Underwriters see only genuine edge cases, and every decision can be audited from the checkpoint history.

Architecture

Applicantweb / branch appUnderwritercredit team UI06Agent Serverthread per loan01Document intakeextract + validate02Bureau checkcredit bureau API03Income verificationbank stmt + payslip03Postgres savercheckpoint/thread08Risk scoringrules + model04Route decisionconditional edge05Underwriter reviewinterrupt()06Finalize decisionaudit + letter07Loan systembooking + notify07LangSmithtraces + time travel08upload docs, thread_idSTARTparallel branchparallel branchbureau reportverified incomescore + flagsclear approve/declineedge caseinterrupt payloadCommand(resume=...)reviewer decisionbook / notifysave each super-stepresume after crashtraces

How it works, step by step

  1. The Agent Server receives the application and starts a run on a thread whose thread_id is the application ID; the graph is compiled with a Postgres checkpointer so every super-step is saved.
  2. The document intake node uses an LLM to extract fields (identity, employer, declared income) from uploaded documents into the typed state, and validates them against a schema before moving on.
  3. Bureau check and income verification run as parallel branches in the same super-step, each writing to its own state key; both external calls carry an idempotency key derived from the application ID so a retry never triggers a second inquiry.
  4. The risk scoring node combines bureau data, verified income and policy rules into a score with reason codes; hard policy stops are deterministic rules, not model output.
  5. A conditional edge routes clear approvals and clear declines straight to finalize, and sends edge cases (borderline score, mismatched income, thin file) to the underwriter review node.
  6. The review node calls interrupt() with a case summary; the run pauses and its state is checkpointed. The underwriter decides in the credit UI, which resumes the same thread_id with Command(resume={decision, reason, reviewer_id}).
  7. The finalize node records the decision, reason codes and reviewer, then books the loan or sends the decline notice through the loan system using an idempotent request keyed by application ID.
  8. If a worker crashes at any point, invoking the graph again with the same thread_id and no new input resumes from the last checkpoint; LangSmith traces and get_state_history give auditors a step-by-step record and allow replay from an earlier checkpoint.

Tech stack

  • LangGraph StateGraph with a TypedDict state
  • PostgresSaver checkpointer (langgraph-checkpoint-postgres)
  • PostgreSQL
  • LangSmith Agent Server for deployment
  • LangSmith tracing
  • LLM with structured output for document extraction
  • Credit bureau and Account Aggregator / bank statement APIs
  • Internal loan management system API

Key design decisions

  • One thread per application, with thread_id equal to the application ID, so resume, audit and support all key off the same identifier.
  • Bureau and income checks are separate nodes in parallel rather than one combined node, so a failure in one does not force a fresh call to the other and each has its own retry policy.
  • The LLM only extracts and summarises; scoring rules and hard declines are deterministic code so decisions are explainable and repeatable.
  • Human review uses interrupt() inside a dedicated node instead of polling a status table, so the pause, the reviewer input and the resume all live in the checkpoint history.
  • Postgres checkpointer in production and InMemorySaver only in local tests, because in-memory state is lost on restart and defeats durable execution.

Common mistakes

  • On resume, the node that called interrupt() runs again from the beginning, so any side effect placed before interrupt() (sending an email, logging a review request) repeats unless it is idempotent or moved to a separate node.
  • Nodes rerun from their start after a crash, so non-idempotent bureau calls can create duplicate hard inquiries on the applicant's credit file; always pass an idempotency key or check state first.
  • Using a new thread_id on retry silently starts a fresh application instead of resuming the old one.
  • Storing raw documents or full bureau reports in graph state bloats every checkpoint; keep references to object storage and only the extracted fields in state.
  • Changing the state schema or node names while runs are paused can break resume of in-flight threads; version the graph and migrate or drain old threads.

What to measure

  • Share of crashed or interrupted runs that resume to completion without manual repair
  • Duplicate external calls per application (bureau inquiries, bookings), which should be zero
  • Share of applications routed to underwriter review, and median time waiting on a human
  • End-to-end turnaround time from submission to decision, split into automated and human time
  • Underwriter override rate versus the model's recommended decision

Most-asked interview questions

Subjective questions 33

1How does a Postgres checkpointer give you durable execution in this underwriting graph, and what does it not handle for you?Asked almost every time

The graph is compiled with a Postgres checkpointer, and every run uses the application ID as its thread_id. After each super-step LangGraph saves the state for that thread, so if a worker dies during income verification, I invoke the graph again with the same thread_id and it continues from the last saved checkpoint rather than from intake. What it does not do is make my side effects safe. A node that was mid-flight reruns from its start, so a bureau pull or booking call inside it can happen twice. I handle that with idempotency keys derived from the application ID and by checking state before calling out. I also keep large documents out of state so checkpoints stay small.

Sourced from Docs by LangChain (MCP)

2Why use the loan application ID as the thread_id, and what trade-offs does that create?Asked almost every time

The thread_id is the key the checkpointer uses to store and look up state, so tying it to the application ID gives one durable history per application. Any worker can resume it, the credit UI knows which thread to resume, and auditors can pull the full checkpoint history from the business key alone. The trade-off is that one thread now means exactly one underwriting attempt. If an applicant reapplies, or you must restart from scratch after a policy change, create a new application ID or a derived thread ID such as appId plus an attempt number. Reusing the old thread would mix two decisions in one history. Also make sure the ID is opaque and contains no PII, because it shows up in logs, traces and URLs.

Sourced from Docs by LangChain (MCP)

3Design the typed state for this underwriting graph. What keys would you include and why?Asked almost every time

I would use a TypedDict or Pydantic model with separate keys for each stage: application_id, extracted_fields (identity, employer, declared income), validation_errors, bureau_result, income_result, risk_score, reason_codes, policy_version, route, and review, which holds decision, reason and reviewer_id. Add final_decision and booking_reference at the end. Bureau and income get their own keys because they are written in parallel in the same super-step. An audit_events list with an operator.add reducer lets each node append entries without overwriting the others. Store references to documents rather than raw files, because state is checkpointed at every super-step and anything you put in it gets copied into Postgres many times.

Sourced from Docs by LangChain (MCP)

4How do you guarantee that resuming a crashed run never causes a second bureau pull?Asked almost every time

Checkpoints alone cannot guarantee this. If the worker crashes after the bureau call returns but before the super-step's writes are saved, that node runs again on resume. So the guarantee comes from two layers. First, the bureau call sends an idempotency key derived from the application ID and the operation, so a repeated request returns the original inquiry result and does not create a new hard inquiry. Second, the node checks state first: if bureau_result is already present, it skips the call. Ideally I also keep a local outbound-request table keyed by the same idempotency key. Then I can confirm what was sent even if the bureau does not support idempotency natively and I need to look up the result before retrying.

Sourced from Docs by LangChain (MCP)

5When an interrupted node resumes, it runs again from the top. How does that shape the review node's code?Asked almost every time

Resume does not continue from the line where interrupt() was called. The whole node runs again, and this time interrupt() returns the resume value. Any code before interrupt() therefore runs twice. In the review node, everything before interrupt() must be pure or idempotent. Building the case summary from state is fine. Sending a notification email, writing an assignment row or calling an external API there is not, unless it is keyed for idempotency. I would put side effects such as notifying the underwriter queue in a separate node before review, and handle the decision itself in finalize. Also never wrap interrupt() in a broad try/except, because it pauses the run by raising an internal exception that must propagate.

Sourced from Docs by LangChain (MCP)

6How do you enforce that no decision is ever issued without a recorded reason and reviewer?Asked almost every time

I enforce it at three layers. First, the resume payload is validated against a schema: decision, reason and reviewer_id are all required, and the reason must be a known code, optionally with free text. Second, finalize acts as a guard. Before calling the loan system it checks that the reason codes and the reviewer are present in state. For automated paths the reviewer is a system identity plus the rule or policy version. If anything is missing, it raises an error or routes back to review, and nothing is booked. Third, the loan system's API rejects a booking or decline request that has no decision record ID. This means even a buggy graph version cannot issue an unrecorded decision.

Sourced from Docs by LangChain (MCP)

7The state holds PAN, income and bureau data. How do you handle security and privacy across checkpoints and traces?Asked almost every time

Checkpoints copy state into Postgres at every super-step, and traces capture node inputs and outputs. That means PII spreads unless you design against it. My measures are these: keep raw documents in an encrypted object store and put only references in state; mask identifiers that scoring does not need; encrypt the Postgres database at rest and consider serializer-level encryption of checkpoint payloads; and restrict database access by role. In LangSmith, configure masking or redaction of inputs and outputs, and limit who can view the project. Set retention periods that match your legal obligations, for example under India's DPDP Act and the lender's own policy, and purge old checkpoints accordingly. Every access to a case should be logged.

Sourced from Docs by LangChain (MCP)

8Checkpointing gives durability, so why do you still need idempotency keys? Explain exactly-once versus at-least-once here.Asked almost every time

Checkpointing gives you at-least-once execution of nodes, not exactly-once side effects. A node's writes are saved only after it finishes. If the worker dies after the external call succeeds but before the checkpoint is written, the node runs again on resume. Retry policies and replays also run nodes again. The external effect, a bureau inquiry or a booking, has already happened in another system that the checkpoint cannot roll back. Exactly-once effect therefore needs the receiving system's cooperation. You send an idempotency key so the duplicate request is recognised and returns the original result. The combination of at-least-once execution and idempotent receivers gives an effectively-once outcome.

Sourced from Docs by LangChain (MCP)

9The loan is booked, then the worker crashes before finalize's checkpoint is saved. Walk through recovery.Asked almost every time

On resume, the graph loads the last saved checkpoint. That checkpoint is before finalize, because finalize never completed, so finalize runs again. It sends the same booking request with the same idempotency key, derived from the application ID. The loan system recognises the key and returns the existing booking reference instead of creating a second loan. Finalize stores that reference and the run completes. Before booking, finalize can also query the loan system for an existing record for this application, which helps if a downstream system's idempotency window has expired. The decision record is also written idempotently, so an upsert on the application ID means no duplicate audit rows.

Sourced from Docs by LangChain (MCP)

10Walk me through how an underwriter approves an edge case using interrupt().Very often asked

The conditional edge after risk scoring sends borderline cases to a review node. That node builds a short summary with the score, reason codes and any mismatches, then calls interrupt() with that payload. The run pauses and the checkpoint is saved, so it can wait for days without holding a worker. The credit UI reads the pending interrupt for that thread and shows it to the underwriter. When they decide, the UI resumes the same thread_id with Command(resume=...) carrying the decision, the reason and the reviewer ID. The review node runs again from its start, interrupt() now returns that value, and the node writes it to state before finalize runs. I keep the review node free of side effects before interrupt() because it reruns on resume.

Sourced from Docs by LangChain (MCP)

11Why keep the LLM out of the actual credit decision, and where does it add value?Very often asked

Lending decisions have to be explainable and consistent, and a regulator or auditor will ask why a specific applicant was declined. So scoring and hard policy stops are deterministic code with reason codes, and the same inputs always give the same result. The LLM adds value at the edges: extracting structured fields from messy payslips and bank statements into a validated schema, flagging inconsistencies between documents, and writing a clear case summary for the underwriter. Its output is checked against the schema before it enters state, and anything it flags pushes the case towards human review rather than straight to a decline. That keeps the model helpful without putting it in charge of the outcome.

Sourced from Docs by LangChain (MCP)

12Bureau check and income verification run in parallel. Why do they write to separate state keys, and what happens if they don't?Very often asked

Both branches run in the same super-step, and their updates are applied together at the end of that step. If two nodes write the same key and the key has no reducer, LangGraph cannot tell which value should win, so it raises an InvalidUpdateError instead of silently picking one. Separate keys, bureau_result and income_result, avoid the conflict and make the data model clearer, since each key has exactly one owner. If you really need a shared key, such as a list of audit events, give it a reducer like operator.add so the writes from both branches are combined. Risk scoring runs only after both branches finish, so it always sees both results.

Sourced from Docs by LangChain (MCP)

13Income verification succeeds but the bureau call fails in the same super-step. What happens on resume?Very often asked

LangGraph saves pending writes for the nodes in a super-step that finished successfully, even when another node in that step fails. When you resume on the same thread_id, it reuses the income verification result and reruns only the bureau branch. You do not pay for the income call twice. This only protects writes that were actually saved. If the whole worker died before the successful node's writes were persisted, that node would run again, which is why both external calls still carry idempotency keys. I would also add a retry policy on the bureau node for transient errors, so a timeout does not fail the run when a quick retry would work.

Sourced from Docs by LangChain (MCP)

14How would you design the idempotency keys, and how do you handle a legitimate re-pull, say when a bureau report has expired?Very often asked

Each key combines the application ID with the operation name, for example appId:bureau_pull and appId:loan_booking. Then each operation is deduplicated on its own, and the bureau key cannot block the booking. When the business really needs a fresh pull, for example because the report is older than the policy allows, that is a new intent, not a retry. I would add an explicit version suffix such as appId:bureau_pull:2, and only a deliberate state change can increment it, typically an underwriter action or a policy rule that records why. The version lives in state, so it is checkpointed. A resume therefore reuses the current version and never bumps it by accident.

Sourced from Docs by LangChain (MCP)

15What logic sits in the conditional edge after risk scoring, and where should the thresholds live?Very often asked

The routing function should be small and deterministic. It reads risk_score, the reason codes and flags such as income_mismatch or thin_file, then returns either 'finalize' or 'review'. Hard policy stops always lead to decline, while borderline scores and data conflicts lead to review. Thresholds should not be hard-coded in the graph. They belong in a versioned policy configuration that risk scoring loads, and the policy version is written into state. The edge then reads only flags that are already computed, so routing is easy to audit and test. An auditor can see which policy version produced which route, and you can change thresholds without redeploying the graph code.

Sourced from Docs by LangChain (MCP)

16The LLM extraction in document intake returns fields that fail schema validation. How should the graph handle it?Very often asked

Never move on with invalid data. The intake node validates the output against a Pydantic schema and records the specific errors in state. A conditional edge then picks a path. If the problem is a parsing error, it can retry extraction a bounded number of times. If the document is unreadable or required fields are missing, the run should interrupt for a document request or for manual data entry by operations staff. Fields extracted with low confidence, or that conflict across documents, should be flagged so risk scoring and review can see them. The LLM only produces candidate values. The schema and the later verification steps decide whether they are trusted.

Sourced from Docs by LangChain (MCP)

17Auditors want to replay a run from an earlier checkpoint. What is the danger, and how do you make it safe?Very often asked

Replaying from a checkpoint re-executes every node after it. In this graph, that could mean calling the bureau again or sending a booking request again. Idempotency keys make replay safe against real side effects, because repeated requests return the original results. For pure investigation, I would rather replay in a separate environment where the external clients are stubbed or put into read-only mode. Forking with update_state to test what would have happened under a different input should also run against sandboxed integrations. For most audit needs you do not need replay at all. get_state_history already shows the state at every step without running anything again.

Sourced from Docs by LangChain (MCP)

18What is the audit record for a decision, and is the checkpoint history enough on its own?Very often asked

The checkpoint history and LangSmith traces are very good engineering evidence. They show state at each super-step, the LLM inputs and outputs, and timings. I would not make them the only system of record. Checkpoint retention, schema changes and trace retention settings are not designed for regulatory record keeping. Finalize should write an immutable decision record to the loan system or an audit table. That record holds the decision, reason codes, reviewer_id, policy version, score, the bureau and income references and a checkpoint_id that links back to the graph state. Auditors start from that record and use checkpoints and traces to drill into how the decision was reached.

Sourced from Docs by LangChain (MCP)

19Two underwriters try to resume the same application at the same time. What happens and how do you prevent it?Very often asked

I prevent it at two levels. In the product, the credit UI assigns each case to one reviewer, and the resume API checks that the caller is the assignee before forwarding the Command. On the runtime side, Agent Server lets you set a multitask strategy for concurrent runs on one thread, such as reject or enqueue. With reject, the second resume fails cleanly instead of racing the first. The finalize step stays idempotent on the application ID in any case. So even if two decisions somehow arrive, the loan system books once, and the conflict is surfaced for investigation instead of resolved silently.

Sourced from Docs by LangChain (MCP)

20Who is allowed to resume an interrupted run, and where should reviewer_id come from?Very often asked

Only an authenticated underwriter with the right role, assigned to that case, and ideally within their approval limits for that loan amount. The credit UI should not let the client submit reviewer_id as free input. The backend takes it from the authenticated session and puts it into the Command(resume=...) payload on the server side. Otherwise anyone who can call the resume endpoint could forge an approver. The backend also checks that the thread is actually waiting at the review interrupt before resuming. For larger loans you might require a second approver, which can be a second interrupt or a separate review node. Both identities are then recorded in the decision.

Sourced from Docs by LangChain (MCP)

21Give a back-of-envelope estimate of checkpoint storage for this workflow.Often asked

I would build it from assumptions and state them clearly, not quote a number. There is a checkpoint per super-step: intake, the parallel verification step, scoring, review before and after the interrupt, and finalize. That is roughly six to eight checkpoints per application, plus pending writes. Storage is about the number of applications per day times checkpoints per run times the average serialized state size. State size depends mostly on whether you store raw document text or only references. That is the main lever, because raw text copied into every checkpoint grows quickly. Then apply your retention policy and compare the result with your Postgres capacity. I would measure real state sizes in staging before committing to the figures.

Sourced from Docs by LangChain (MCP)

22An edge case sits in underwriter review for three days. What resources does the paused run consume?Often asked

Almost nothing apart from storage. When interrupt() fires, the state is checkpointed and the run ends for now. No worker, thread or connection stays held while the underwriter thinks. The thread is just rows in Postgres until a Command(resume=...) arrives on that thread_id, and any worker can pick it up. The real concerns are business ones, not compute. You need SLA tracking and escalation for stale reviews, and the bureau data may expire during the wait. I would store review_requested_at in state and have a scheduled job surface overdue cases. Finalize should also check data freshness, so a long wait does not lead to a decision on stale data.

Sourced from Docs by LangChain (MCP)

23How do you handle transient failures such as bureau timeouts without breaking idempotency?Often asked

Add a RetryPolicy when adding the node, with bounded attempts, exponential backoff, and retries only on transient exception types such as timeouts and 5xx errors. Validation errors and 4xx errors should fail fast. Every retry reuses the same idempotency key, so a request that actually reached the bureau but timed out on the response does not become a second inquiry. If retries are exhausted, the run fails but its state stays checkpointed. Operations can resume it later on the same thread_id. Alternatively, a conditional route can send it to a manual queue. A circuit breaker around the bureau client stops many runs from hammering a degraded provider at once.

Sourced from Docs by LangChain (MCP)

24You need to change the graph while hundreds of applications are paused at review. How do you deploy safely?Often asked

Paused threads resume with whatever graph code is deployed at that moment, so changes must be compatible with the checkpoints already saved. Safe changes are adding optional state keys with defaults and changing a node's internals. Risky changes are renaming or removing the node where runs are paused, or changing state types incompatibly. For those, keep the old node name until existing threads drain, or run a separate graph deployment for old versions. Store graph_version and policy_version in state so each run's lineage is clear. Before releasing, I test by loading real staging checkpoints into the new graph and resuming them.

Sourced from Docs by LangChain (MCP)

25Would you use a PostgresStore in this workflow? What belongs there versus in the checkpointer?Often asked

The checkpointer holds per-thread state, meaning everything about one application's run. A store holds long-term memory shared across threads, organised by namespace tuples. Useful candidates for the store are cross-application facts such as verified employer records under a namespace like ('employers', employer_id), or reviewer notes on recurring document patterns. I would be careful with personal data. Keeping an applicant's details in a store for later applications needs a legal basis, purpose limitation and a retention policy, and the bureau or loan system is usually the right source of truth for history. Semantic search over a store is fine for things like policy guidance, but not for retrieving data that a decision depends on.

Sourced from Docs by LangChain (MCP)

26How would you test that resume after a crash never duplicates a side effect?Often asked

Use InMemorySaver or a throwaway Postgres in tests, and replace the bureau and loan clients with fakes that count calls per idempotency key. Then inject failures at each risky point: raise an exception after the external call returns but before the node finishes, kill the process between super-steps, and fail one parallel branch. After each failure, invoke with None on the same thread_id and assert that the call count per key is still one and that the final state is correct. Also test interrupt resume twice with the same payload, replay from an earlier checkpoint, and finalize with a missing reason, which must not book anything.

Sourced from Docs by LangChain (MCP)

27An underwriter overrides the model's recommendation. How do you handle overrides responsibly?Often asked

Overrides are expected, because that is why there is a review step, but they must be recorded and monitored. The resume payload records the decision, a structured reason code, free-text justification and reviewer_id, next to the original score and route, so an override is visible as a difference between the two. Check approval limits: overrides above a certain loan amount, or against a hard policy stop, should need a second approver or be blocked entirely, because hard stops are deterministic rules. Override rates by reviewer and segment feed back into policy tuning and fairness reviews. If reviewers keep overriding a rule, the rule may need to change.

Sourced from Docs by LangChain (MCP)

28Policy thresholds change while applications are mid-flight. Which version applies, and how do you show it?Often asked

Decide the rule explicitly and record it. A common choice is that the policy version at scoring time applies. Risk scoring writes policy_version into state, and that version flows into the decision record. A run resumed after a crash uses the stored version, not whatever is current, so a restart does not change the outcome. If regulation or business requires re-scoring under the new policy, do it as a deliberate action, for example by forking with update_state or running a new attempt. Both cases are logged. Never re-evaluate silently on resume, because then the same application could get different decisions depending on when a worker happened to crash.

Sourced from Docs by LangChain (MCP)

29What do LangGraph's durability modes trade off, and which would you pick here?Often asked

Recent LangGraph versions let you choose when checkpoints are persisted. 'sync' writes each checkpoint before the next step starts. That is the strongest guarantee and costs some latency. 'async' writes in the background while execution continues, which is faster but leaves a small window where a crash can lose the latest step. 'exit' persists only when the run finishes or pauses, which is cheapest but loses intermediate progress. For underwriting, with external side effects and regulatory scrutiny, I would lean towards 'sync'. The run is dominated by external API and review latency anyway, so the extra write time barely matters, and idempotency still protects the window that remains.

Sourced from Docs by LangChain (MCP)

30An application has several uploaded documents. How would you process them in parallel with Send?Often asked

A conditional edge from intake planning returns a list of Send objects, one per document. Each Send targets an extract_document node with a small per-item state containing the document reference and type. These run in parallel as a map step. Each returns its extracted fields in a list key with an operator.add reducer, so the results accumulate without conflict. A reduce node then merges them, resolves conflicts between documents, and validates the merged result against the schema. Because each branch reads from a document store by reference, the payloads stay small. Branches that completed are not rerun if another fails, thanks to pending writes.

Sourced from Docs by LangChain (MCP)

31Which streaming modes would you use to show progress in the credit UI or an applicant portal?Sometimes asked

For a progress view, 'updates' streams each node's state change as it completes, so the UI can show 'documents verified' or 'bureau check done' without the full state. 'custom' lets a node emit explicit progress events, which is cleaner than exposing internal state shapes to the client. 'messages' streams LLM tokens with node metadata, which is useful only if you show live extraction output, and usually not to applicants. 'values' sends full state after each step, which is heavy and may expose PII, so I would keep it for internal tools. 'debug' is for developers. In every case, filter what reaches the browser.

Sourced from Docs by LangChain (MCP)

32Should income verification be a subgraph? When is that worth it?Sometimes asked

Make it a subgraph if it has its own multi-step flow, for example fetching bank statements, parsing them, matching them with payroll data, and possibly interrupting for a missing statement. A subgraph keeps that complexity inside one unit, lets a separate team own and test it, and still shows up in traces and checkpoint history. Its state can share keys with the parent or map to them through a wrapper function. If it is just one API call with some validation, a plain node is simpler. Note that interrupts inside a subgraph also pause the parent thread, so the resume flow stays the same for the UI.

Sourced from Docs by LangChain (MCP)

33Agent Server exposes an MCP endpoint at /mcp. Should the underwriting graph be exposed through it?Sometimes asked

Exposing it lets other agents or tools start an assessment over MCP, which can help internal automation. But this graph moves money and handles personal data, so I would not expose it broadly. If it is exposed, put it behind the same authentication and authorisation as the main API, use service identities with narrow scopes, and probably expose only safe operations such as starting an application or reading status. Resuming a review must never be possible through an agent channel, because a model must not be able to supply a decision and reviewer. Log and rate-limit all MCP calls like any other client.

Sourced from Docs by LangChain (MCP)

Objective questions 33

1A node calls interrupt() and later the client sends Command(resume=value). What happens inside that node?Asked almost every time
  1. AThe node runs again from its beginning and interrupt() returns the resume valueCorrect answer
  2. BThe graph skips the node and moves to the next edge
  3. CThe whole graph restarts from START
  4. DExecution continues from the line right after interrupt()

Why: On resume LangGraph re-executes the interrupted node from its start, and this time interrupt() returns the value passed in Command(resume=...). That is why code before interrupt() must be safe to repeat.

Sourced from Docs by LangChain (MCP)

2What should the thread_id be for each loan application's run?Asked almost every time
  1. AThe underwriter's user ID
  2. BThe current date
  3. CA random UUID generated per invocation
  4. DThe loan application IDCorrect answer

Why: The checkpointer stores state per thread_id, so keying it to the application ID lets any worker or the credit UI find and resume that application's run.

Sourced from Docs by LangChain (MCP)

3After a worker crash, how do you resume a run from its last checkpoint?Asked almost every time
  1. AInvoke with the original application payload and a new thread_id
  2. BCall update_state with an empty dict
  3. CRestart the Agent Server
  4. DInvoke with None as input and the same thread_id in the configCorrect answer

Why: Passing None as input with the same thread_id tells LangGraph to continue from the saved checkpoint instead of starting new input.

Sourced from Docs by LangChain (MCP)

4When a node that called interrupt() is resumed, what runs?Asked almost every time
  1. AOnly the lines after interrupt()
  2. BThe next node directly, skipping this one
  3. CThe entire node again from the start, with interrupt() returning the resume valueCorrect answer
  4. DThe whole graph from START

Why: LangGraph re-executes the interrupted node from the beginning. This time interrupt() returns the value passed in Command(resume=...).

Sourced from Docs by LangChain (MCP)

5Where should reviewer_id in the resume payload come from?Asked almost every time
  1. AA free-text field the underwriter types
  2. BThe LLM's case summary
  3. CThe authenticated session, added on the server sideCorrect answer
  4. DThe applicant's upload form

Why: Deriving the reviewer from server-side authentication prevents a client from forging who approved the decision.

Sourced from Docs by LangChain (MCP)

6The loan is booked, then the worker crashes before finalize's checkpoint is saved. What prevents a duplicate loan on resume?Asked almost every time
  1. AThe interrupt payload is replayed
  2. BThe Postgres checkpointer rolls back the booking
  3. CThe loan system deduplicates the idempotent request keyed by application IDCorrect answer
  4. DLangGraph skips any node that has started

Why: Finalize runs again on resume, so only the downstream system's idempotency on the stable key stops a second booking.

Sourced from Docs by LangChain (MCP)

7Finalize receives a decision with no reason code. What should it do?Asked almost every time
  1. ARefuse to book: raise an error or route back to reviewCorrect answer
  2. BAsk the LLM to invent a reason
  3. CBook the loan and log a warning
  4. DFill in a default reason

Why: The workflow must never issue a decision without a recorded reason, so finalize must block the decision.

Sourced from Docs by LangChain (MCP)

8After a worker crash, what must the client supply to resume the same loan application from its last checkpoint?Very often asked
  1. ANothing; LangGraph resumes all threads automatically
  2. BA new thread_id and the original input
  3. CThe same thread_id in the configCorrect answer
  4. DThe checkpoint's raw SQL row

Why: Checkpoints are stored per thread_id, so invoking the graph again with the same thread_id lets it load the latest checkpoint and continue. A new thread_id starts a fresh run.

Sourced from Docs by LangChain (MCP)

9Which object does the credit UI send to continue a paused review?Very often asked
  1. Ainterrupt(decision)
  2. BEND
  3. CSend('review', decision)
  4. DCommand(resume={decision, reason, reviewer_id})Correct answer

Why: Command(resume=...) supplies the value that interrupt() returns inside the paused node.

Sourced from Docs by LangChain (MCP)

10Two parallel nodes in the same super-step write the same state key, and that key has no reducer. What happens?Very often asked
  1. AThe values are concatenated automatically
  2. BLangGraph raises an InvalidUpdateErrorCorrect answer
  3. CThe second node is skipped
  4. DThe last writer silently wins

Why: Without a reducer, concurrent writes to one key are ambiguous, so LangGraph raises an error rather than picking one.

Sourced from Docs by LangChain (MCP)

11When does a checkpointer save graph state?Very often asked
  1. AOnce per hour
  2. BOnly when the run reaches END
  3. CAt every super-stepCorrect answer
  4. DOnly when interrupt() is called

Why: Checkpointers save state per thread at every super-step, which is what enables resume and time travel.

Sourced from Docs by LangChain (MCP)

12How should hard policy stops, such as an ineligible age or a blacklisted applicant, be implemented?Very often asked
  1. AAs deterministic rules in risk scoringCorrect answer
  2. BAs underwriter-only decisions
  3. CAs a streaming mode filter
  4. DAs LLM judgments with a high temperature

Why: Hard stops must be reproducible and auditable, so they are deterministic rules and not model output.

Sourced from Docs by LangChain (MCP)

13Which idempotency key design is best for the bureau pull?Very often asked
  1. AA key derived from the application ID plus the operationCorrect answer
  2. BThe worker's hostname
  3. CA fresh random key on every attempt
  4. DThe current timestamp

Why: A stable key derived from the application ID and operation is identical across retries, so the bureau can deduplicate repeated requests.

Sourced from Docs by LangChain (MCP)

14In a super-step, income verification succeeds and the bureau node fails. On resume, what normally happens?Very often asked
  1. AOnly the bureau node runs again, because the successful node's pending writes were savedCorrect answer
  2. BThe graph restarts from START
  3. CThe run cannot be resumed
  4. DBoth nodes run again

Why: LangGraph keeps pending writes from nodes that completed successfully in a failed super-step, so they are not re-executed.

Sourced from Docs by LangChain (MCP)

15Where should a non-idempotent side effect, such as notifying the review queue, be placed relative to interrupt()?Very often asked
  1. AInside a try/except that wraps interrupt()
  2. BIn a separate node, or made idempotentCorrect answer
  3. CInside the routing function
  4. DBefore interrupt() in the same node

Why: Code before interrupt() runs again on resume, so side effects there would repeat unless they are moved to another node or made idempotent.

Sourced from Docs by LangChain (MCP)

16Which practice is least appropriate for PII in this workflow?Very often asked
  1. AMasking inputs and outputs in LangSmith traces
  2. BLogging full bureau reports in plain text to tracesCorrect answer
  3. CEncrypting the checkpoint database at rest
  4. DStoring document references instead of raw files in state

Why: Traces and checkpoints copy data widely, so unredacted bureau reports in traces expose sensitive data unnecessarily.

Sourced from Docs by LangChain (MCP)

17LLM-extracted fields fail schema validation. What is the correct next step?Very often asked
  1. ARetry within a bound, or route to a human or document requestCorrect answer
  2. BApprove the application by default
  3. CDelete the thread
  4. DPass them to risk scoring anyway

Why: Invalid extraction must never reach scoring; it needs a bounded retry or human correction.

Sourced from Docs by LangChain (MCP)

18Which checkpointer choice fits a production underwriting service that must survive restarts?Often asked
  1. AInMemorySaver
  2. BNo checkpointer, with state stored in local variables
  3. CA Postgres checkpointerCorrect answer
  4. DInMemoryStore

Why: A Postgres checkpointer persists state outside the process, so runs survive restarts and deploys. InMemorySaver is meant for development, and a Store holds long-term memory across threads rather than per-run checkpoints.

Sourced from Docs by LangChain (MCP)

19Which reducer lets several nodes append audit events to a shared list key?Often asked
  1. Amax
  2. BNo reducer is needed
  3. Coperator.addCorrect answer
  4. Doperator.eq

Why: operator.add combines list updates by concatenation, so concurrent appends are all kept.

Sourced from Docs by LangChain (MCP)

20Which mechanism fans out per-document extraction with each branch receiving its own per-item state?Often asked
  1. AA subgraph with interrupt()
  2. BCommand(resume=...)
  3. CA PostgresStore namespace
  4. DReturning Send objects from a conditional edgeCorrect answer

Why: Send objects returned from a conditional edge dispatch a node once per item with item-specific state, which is the map-reduce pattern.

Sourced from Docs by LangChain (MCP)

21An auditor wants to inspect state at each step without re-running anything. What should they use?Often asked
  1. AInvoke the graph again with the original input
  2. BStream with 'messages' mode
  3. Cupdate_state with the old values
  4. Dget_state_history on the threadCorrect answer

Why: get_state_history returns the saved checkpoints for the thread, so past state can be inspected without executing nodes.

Sourced from Docs by LangChain (MCP)

22To test a different underwriter input from an earlier point without altering the original history, you would…Often asked
  1. ADelete later checkpoints in Postgres
  2. BFork with update_state at an earlier checkpoint and continue from thereCorrect answer
  3. CStart a run with a new thread_id and no input
  4. DChange the reducer on the review key

Why: update_state at an earlier checkpoint creates a fork, leaving the original checkpoints intact.

Sourced from Docs by LangChain (MCP)

23Which checkpointer is intended for local development and tests?Often asked
  1. AInMemoryStore
  2. BLangSmith traces
  3. CInMemorySaverCorrect answer
  4. DPostgresStore

Why: InMemorySaver keeps checkpoints in memory, which is fine for development but does not survive a process restart.

Sourced from Docs by LangChain (MCP)

24Where should data that is shared across many applications, such as verified employer records, live?Often asked
  1. AThe 'debug' stream
  2. BThe per-thread checkpoint state
  3. CA Store organised by namespace tuplesCorrect answer
  4. DThe interrupt payload

Why: Stores provide long-term memory across threads, while checkpoints are scoped to a single thread.

Sourced from Docs by LangChain (MCP)

25Which streaming mode emits only each node's state changes as they happen?Often asked
  1. Acustom
  2. BupdatesCorrect answer
  3. Cvalues
  4. Dmessages

Why: 'updates' streams per-node deltas, while 'values' streams the full state after each step.

Sourced from Docs by LangChain (MCP)

26A run waits at the review interrupt for several days. What does it consume?Often asked
  1. AAn open database transaction
  2. BCheckpoint storage only, until a resume arrivesCorrect answer
  3. CA held LLM connection
  4. DA dedicated worker process for the whole period

Why: An interrupted run is persisted as a checkpoint and released, so no compute is held while it waits.

Sourced from Docs by LangChain (MCP)

27On Agent Server, what controls what happens when a second run is started on a thread that already has an active run?Often asked
  1. AThe streaming mode
  2. BThe Store namespace
  3. CThe reducer on the state key
  4. DThe multitask strategy, such as reject or enqueueCorrect answer

Why: Agent Server's multitask strategies decide how concurrent runs on the same thread are handled.

Sourced from Docs by LangChain (MCP)

28What is the safest way to handle transient bureau timeouts?Often asked
  1. ASkipping the bureau check
  2. BA RetryPolicy on transient errors, reusing the same idempotency keyCorrect answer
  3. CRetrying with a new idempotency key each time
  4. DRetrying forever with no backoff

Why: Bounded retries with the same key handle transient faults without creating a second inquiry.

Sourced from Docs by LangChain (MCP)

29Which streaming mode delivers LLM tokens together with node metadata?Sometimes asked
  1. Avalues
  2. Bupdates
  3. CmessagesCorrect answer
  4. Ddebug

Why: The 'messages' mode streams LLM tokens along with metadata about the node that produced them.

Sourced from Docs by LangChain (MCP)

30Which construct lets a node both route to the next node and update state from inside the node?Sometimes asked
  1. ASend(node, state)
  2. BCommand(goto=..., update=...)Correct answer
  3. Cinterrupt()
  4. DSTART

Why: Command with goto and update performs routing and a state update together, as used in handoffs.

Sourced from Docs by LangChain (MCP)

31At what path does a LangSmith Agent Server deployment expose its MCP endpoint?Sometimes asked
  1. A/store
  2. B/mcpCorrect answer
  3. C/threads
  4. D/runs/stream

Why: The reference notes say agents deployed on Agent Server are exposed over an MCP endpoint at /mcp.

Sourced from Docs by LangChain (MCP)

32A node calls interrupt() twice. On resume, how are resume values matched to the calls?Sometimes asked
  1. ABy a random ID
  2. BBy the order of the interrupt() calls within the nodeCorrect answer
  3. CBy node name only
  4. DThey cannot be matched; only one interrupt per graph is allowed

Why: Resume values are matched to interrupt() calls by their order of execution within the node, which is why that order must stay stable.

Sourced from Docs by LangChain (MCP)

33Which of these is not a LangGraph streaming mode?Sometimes asked
  1. AsnapshotsCorrect answer
  2. Bdebug
  3. Cvalues
  4. Dcustom

Why: The streaming modes are values, updates, messages, custom and debug; there is no 'snapshots' mode.

Sourced from Docs by LangChain (MCP)

Track your progress through every blueprint in your dashboard.

Start learning