# How iroh Bi-Directional QUIC Streams Differ from Uni-Directional: A Complete Guide

> Discover the key differences between iroh's bi-directional and uni-directional QUIC streams. Understand one-way vs. full-duplex communication for efficient data transfer.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: deep-dive
- Published: 2026-07-14

---

**Uni-directional streams allow only the creator to send data (one-way), while bi-directional streams enable full-duplex communication where both endpoints can send and receive over the same logical channel.**

Iroh, developed by n0-computer, builds on the quinn QUIC implementation to provide robust peer-to-peer networking. Understanding how **iroh bi-directional QUIC streams** differ from their uni-directional counterparts is essential for optimizing memory usage and designing efficient protocols. This guide examines the API differences, configuration options, and architectural implications directly from the source code.

## Core Differences Between Stream Types

Iroh exposes two distinct stream types through its public API, each mapping to different QUIC stream types and use cases.

| Aspect | Uni-Directional Stream | Bi-Directional Stream |
|--------|------------------------|----------------------|
| **Directionality** | Only the endpoint that creates the stream can *send* data; the remote side can only *receive*. | Both endpoints can *send* and *receive* on the same logical stream (full-duplex). |
| **Underlying QUIC** | Maps to QUIC uni-directional stream (type 0). | Maps to QUIC bidirectional stream (type 1). |
| **API Return Type** | `SendStream` (write-only). | Tuple `(SendStream, RecvStream)` for simultaneous read/write. |
| **Primary Use Case** | Fire-and-forget notifications, one-way uploads, logging. | Request/response protocols, interactive sessions, echo services. |

## API Implementation in iroh

The stream creation methods are implemented in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs), providing distinct interfaces for each stream type.

### Opening Uni-Directional Streams

The `Connection::open_uni()` method creates a write-only stream handle. Internally, this forwards to `self.inner.open_uni()` within the QUIC implementation, creating a stream that can only be written to by the creator.

```rust
// Connect to remote endpoint
let conn = endpoint.connect(remote_addr, b"my-alpn").await?;

// open_uni returns a SendStream that can only write
let mut send = conn.open_uni().await?;
send.write_all(b"fire-and-forget payload").await?;
send.finish()?;  // Close the sending side

```

The peer receives this stream through `Connection::accept_uni()`, which returns a read-only handle corresponding to the `SendStream` created by the opener.

### Opening Bi-Directional Streams

For full-duplex communication, `Connection::open_bi()` returns both a sender and receiver, allowing simultaneous data flow in both directions.

```rust
// open_bi returns both SendStream and RecvStream
let (mut send, mut recv) = conn.open_bi().await?;

// Send request
send.write_all(b"request data").await?;
send.finish()?;  // Signal end of request

// Read response on same stream
let mut response = Vec::new();
recv.read_to_end(&mut response).await?;

```

The peer receives this pair through `Connection::accept_bi()`, enabling immediate bidirectional communication without opening a separate stream.

## Configuration and Resource Limits

Stream limits are configured via `QuicTransportConfigBuilder` in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs). These settings prevent resource exhaustion by controlling concurrency at the QUIC layer.

### Concurrent Stream Limits

- **`max_concurrent_uni_streams`**: Controls how many uni-directional streams can exist simultaneously
- **`max_concurrent_bidi_streams`**: Controls the limit for bi-directional streams

```rust
use iroh::endpoint::QuicTransportConfigBuilder;

let transport_cfg = QuicTransportConfigBuilder::default()
    .max_concurrent_uni_streams(VarInt::from_u32(100))
    .max_concurrent_bidi_streams(VarInt::from_u32(20))
    .build();

```

### Flow Control Windows

Both stream types respect the `stream_receive_window` setting defined in `QuicTransportConfigBuilder`. This window size applies to the receive buffer allocation for individual streams, while the global `receive_window` governs the entire connection. These flow control mechanisms apply identically to uni- and bi-directional streams.

## Memory and Performance Considerations

**Uni-directional streams** avoid allocating receive buffers on the sending side because the creator cannot read from them. This reduces memory overhead for one-way traffic patterns like telemetry or logging, where responses are unnecessary.

**Bi-directional streams** require buffer allocation for both directions but eliminate the overhead of opening separate streams for request/response patterns. For protocols requiring acknowledgment or conversation, they reduce latency by avoiding the stream establishment overhead of two uni-directional channels.

## Practical Examples from the Repository

The [`iroh/examples/remote-info.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/remote-info.rs) file demonstrates the uni-directional pattern using `conn.open_uni().await` to push information without awaiting a response.

For bi-directional patterns, the echo protocol implementation (referenced in [`iroh/examples/echo.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/echo.rs)) shows how the same stream handles both request and response, matching the pattern shown in the repository's README for interactive QUIC protocols.

## Summary

- **Uni-directional streams** (`open_uni`/`accept_uni`) provide one-way write access from creator to peer, mapping to QUIC type 0 streams and reducing memory footprint for send-only operations.
- **Bi-directional streams** (`open_bi`/`accept_bi`) return `(SendStream, RecvStream)` tuples enabling full-duplex communication over QUIC type 1 streams, essential for request/response patterns.
- **Configuration** happens through `QuicTransportConfigBuilder` methods `max_concurrent_uni_streams` and `max_concurrent_bidi_streams` in [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs).
- **Resource efficiency** favors uni-directional streams for one-way data pushes, while bi-directional streams optimize interactive protocols.

## Frequently Asked Questions

### When should I use uni-directional streams vs bi-directional in iroh?

Use **uni-directional streams** when you need simplex communication such as fire-and-forget notifications, log streaming, or one-way file uploads where the sender requires no acknowledgment. Use **bi-directional streams** for interactive protocols requiring request/response cycles, such as RPC calls, chat messages, or command/control interfaces where both peers must exchange data.

### How do I configure stream limits in iroh?

Configure limits through `QuicTransportConfigBuilder` before building your endpoint. Call `max_concurrent_uni_streams()` and `max_concurrent_bidi_streams()` with appropriate `VarInt` values to set hard limits enforced by the QUIC layer. These settings prevent resource exhaustion by rejecting new stream creation attempts beyond the configured thresholds.

### Can I convert a uni-directional stream to bi-directional?

No, stream directionality is fixed at creation time in QUIC. A uni-directional stream (type 0) cannot be upgraded to bi-directional. If you need bidirectional communication after opening a uni-directional stream, you must either open a second uni-directional stream in the opposite direction or open a new bi-directional stream using `open_bi()`.

### What happens if I exceed the concurrent stream limits?

The QUIC implementation enforces these limits at the protocol level. When `max_concurrent_uni_streams` or `max_concurrent_bidi_streams` is reached, subsequent calls to `open_uni()` or `open_bi()` will block or return an error depending on the specific QUIC configuration and async runtime behavior, preventing memory exhaustion and maintaining connection stability.