# Streaming vs. blocking

Web page: https://academy.memberpress.ai/courses/api-fundamentals/l3/

Lesson 3 of 9 in [Anthropic API Fundamentals](https://academy.memberpress.ai/courses/api-fundamentals/), 16 min.

Video: https://www.youtube.com/watch?v=rZFskyBMZn0

Blocking and streaming use the same endpoint and the same request body. One field changes. The experience for a human reading the output changes completely.

**Blocking mode — the full response arrives at once**

Everything you have written so far uses blocking mode. The connection stays open until the model finishes, then the full JSON response arrives. Your code reads `content[0].text` and moves on.

This is the right choice when the response feeds downstream work: a database write, another API call, a test assertion. You cannot do any of that work on partial text.

**Streaming mode — tokens arrive as they are generated**

Add `"stream": true` to the request body. The endpoint switches from returning a single JSON object to returning a stream of server-sent events (SSE).

```bash
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-4-5",
    "max_tokens": 512,
    "stream": true,
    "messages": [{"role": "user", "content": "Count to ten slowly, one word per line."}]
  }'
```

The terminal will print raw SSE lines as they arrive. You will see events like `event: content_block_delta` with `delta.text` containing the next few characters, followed eventually by `event: message_stop`.

**Handling streaming in Python**

```python
# scripts/stream.py
import anthropic

client = anthropic.Anthropic()

with client.messages.stream(
    model="claude-opus-4-5",
    max_tokens=512,
    messages=[{"role": "user", "content": "Write a two-sentence summary of HTTP."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    print()  # newline after stream completes
    final = stream.get_final_message()
    print(f"\ninput_tokens={final.usage.input_tokens} output_tokens={final.usage.output_tokens}")
```

The `text_stream` iterator yields each partial text delta. `get_final_message()` returns the complete message object — including `usage` — after `message_stop`. You cannot access `usage` mid-stream because token counts are not finalized until generation ends.

**Handling streaming in Node.js**

Many web apps handle Claude responses in a JavaScript backend. The pattern is the same, just spelled differently:

```javascript
// scripts/stream.mjs
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const stream = await client.messages.stream({
  model: "claude-opus-4-5",
  max_tokens: 512,
  messages: [{ role: "user", content: "Write a two-sentence summary of HTTP." }],
});

for await (const chunk of stream) {
  if (
    chunk.type === "content_block_delta" &&
    chunk.delta.type === "text_delta"
  ) {
    process.stdout.write(chunk.delta.text);
  }
}

const final = await stream.finalMessage();
console.log(`\ninput_tokens=${final.usage.input_tokens}`);
```

Run with `node scripts/stream.mjs` after `npm install @anthropic-ai/sdk`. The chunk shape mirrors the Python SDK: `content_block_delta` events carry the text, `message_stop` terminates the stream.

**When to choose each mode**

Streaming makes a ten-second response feel interactive because the first tokens arrive in under a second. For chat interfaces, search results, and any output a human reads live, streaming is the better choice.

Blocking is simpler to reason about. The response is either there or it is not. Use it for batch processing, pipelines, and any situation where the full response is needed before the next step runs.

**The `message_stop` event matters**

In a raw SSE implementation (not using the SDK's `messages.stream` helper), you must detect `event: message_stop` to know the stream is done. Stopping early — closing the connection before this event — means you silently miss the end of the response. The SDK handles this for you, but if you are implementing streaming in a language without an official SDK, this is the failure mode to watch for.

**If you get stuck**

- Terminal prints nothing and hangs: the `stream: true` flag may not have been parsed correctly. Check that your JSON is valid and that the field is a boolean, not the string `"true"`.
- Stream ends mid-sentence: `max_tokens` was hit. Check `stop_reason` in the final message event — it will say `max_tokens` instead of `end_turn`.
- `usage` is all zeros: you are reading `usage` from a partial event rather than from the final message. Call `get_final_message()` (Python) or `finalMessage()` (Node) after the stream completes.
- SSE lines look garbled: each event is prefixed with `data: ` — parse the JSON after that prefix, not the whole line.
- Node: `for await` loop exits before the stream finishes: confirm you are using `stream` (the async iterable) not `stream.on('data', ...)`. The SDK exposes both; the `for await` form is safer.

**Cross-reference**

l7 (Wrap a call in a small CLI) shows how to wire streaming output to stdout cleanly via the `--stream` flag. l4 (Errors, retries, and timeouts) covers what happens when a streaming connection drops mid-response.

**Deliverable**

Run the same prompt in both blocking and streaming mode. Print the elapsed wall time for each. Confirm that the accumulated streaming text matches the blocking response text character-for-character.

## Try these prompts

- Write the Python code to stream a Claude response and print each token to stdout as it arrives, then print the final token count.
- When would you choose blocking mode over streaming? Name two concrete scenarios where blocking is the right choice.
- Write the Node.js for-await loop that reads content_block_delta events from a streaming Claude response.

Next: [Errors, retries, and timeouts](https://academy.memberpress.ai/courses/api-fundamentals/l4/)
