Managing Large Lists in Phoenix LiveView with Streams
Learn how to use Phoenix LiveView Streams to handle large data collections without exhausting server memory or slowing down WebSocket updates.
09 Jun 2026, 15:41 UTC

The Memory Trap of Large Lists
In a standard Phoenix LiveView, the state of your UI is stored in socket.assigns. When you fetch a list of 500 items from a database and assign them to the socket, those 500 items live in the server's memory for the duration of the user's session. Worse, every time a single item in that list changes, LiveView must track the difference and send the update over the WebSocket.
As the list grows, two things happen: server memory usage spikes per user, and the "diff" calculations become more expensive. If you have 1,000 concurrent users each holding a list of 500 records in their assigns, you are quickly consuming significant BEAM memory. This is where Streams come in.
What are LiveView Streams?
Streams allow you to render a collection of items without keeping the entire collection in the server's memory. Instead of storing the list in socket.assigns, LiveView sends the items to the browser once and then only sends updates (inserts, deletes, or replaces) for specific items. The server remembers the structure of the list, but not the full data of every single element.
Implementing a Streamed List
To use streams, you replace a standard for loop in your HEEx template with the phx-update="stream" attribute. This tells LiveView to treat the collection as a stream rather than a static list.
Example: A Real-time Log Viewer
In this scenario, we want to display a list of system logs that updates as new entries arrive, without bloating the server process memory.
# lib/my_app_web/live/log_live.ex
defmodule MyAppWeb.LogLive do
use MyAppWeb, :live
def mount(_params, _session, socket) do
# Fetch initial data
logs = Log.list_recent()
# Use stream/4 to initialize the stream
# :logs is the stream name, logs is the data
{:ok, stream(socket, :logs, logs)}
end
def handle_event("add_log", %{"message" => msg}, socket) do
# Create a new log entry
new_log = Log.create(%{message: msg})
# Use stream_insert to add a single item to the top of the list
# This does NOT require reloading the whole list from the DB
{:noreply, stream_insert(socket, :logs, new_log)}
end
end
# lib/my_app_web/live/log_live.html.heex
<%= log.message %>
Execution and Verification
To implement this, ensure you are using Phoenix 1.7+ and LiveView 1.0+. Run the following commands in your terminal:
- Start your server:
mix phx.server(Run as a standard user with access to the app directory). - Open your browser's Developer Tools > Network tab and filter by WS (WebSockets).
- Trigger the
add_logevent. - Expected Result: You should see a small JSON payload containing only the new log entry, rather than a full re-render of the entire list container.
Trade-offs and Limitations
Streams are powerful, but they introduce a specific constraint: the items must have a unique identifier (usually a primary key). LiveView uses this ID to generate the dom_id used for patching the HTML. If your data lacks a unique ID, streams will not work correctly.
Additionally, because the server no longer holds the full list in memory, you cannot perform server-side operations like Enum.map or Enum.filter on the streamed collection within your handle_event callbacks. If you need to calculate a total or filter the list, you must re-query the database or maintain a separate counter in your assigns.
Decision Matrix: Assigns vs. Streams
| Feature | Standard Assigns | Streams |
|---|---|---|
| Memory Usage | High (scales with list size) | Low (constant per stream) |
| Update Speed | Slower for large lists | Fast (targeted patches) |
| Data Access | Available in socket.assigns |
Must re-query DB for full list |
| Requirement | Any Elixir list | Items must have unique IDs |
Closing Action
Audit your current LiveViews for any for loops iterating over collections larger than 50 items. If those lists are updated dynamically, migrate them to phx-update="stream" to reduce your server's memory footprint and improve the responsiveness of your UI.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.