# How the Interaction Graph Tracks User Interactions for Recommendation Feedback Loops

> Discover how Twitter's Interaction Graph tracks user interactions to build powerful recommendation feedback loops using billions of real-time signals. Learn its core role in the algorithm.

- Repository: [X (fka Twitter)/the-algorithm](https://github.com/twitter/the-algorithm)
- Tags: deep-dive
- Published: 2026-03-03

---

**The Interaction Graph serves as Twitter's core user-interaction ledger, aggregating billions of signals into a time-weighted graph of users and directed interactions to power real-time recommendation feedback loops.**

The Interaction Graph is the foundational data structure behind Twitter's recommendation pipelines, implemented in the `twitter/the-algorithm` repository. It continuously ingests low-level user signals—follows, retweets, likes, mentions, and dwell time—and compiles them into a dense, temporally decayed graph that drives ranking models across Home Timeline, Push notifications, and other surfaces.

## Data Ingestion and Graph Construction

Raw event streams from client logs, address-book imports, and direct interactions feed into a family of Scio jobs located under `src/scala/com/twitter/interaction_graph/scio/agg_*`. Each job extracts edge-level features (e.g., `num_retweets`, `num_follows`) and vertex-level features (e.g., `total_dwell_time`) and persists them as Thrift structs defined in `src/thrift/com/twitter/interaction_graph/interaction_graph.thrift`.

The following Scala snippet demonstrates how daily edge features are read and combined from multiple sources:

```scala
// src/scala/com/twitter/interaction_graph/scio/agg_direct_interactions/InteractionGraphAggDirectInteractionsJob.scala
val directInteractionsEdgeFeatures = source.readDirectInteractionsFeatures(dateInterval)
val aggregatedEdge = FeatureGeneratorUtil.combineEdgeFeatures(
  clientEventLogsEdgeFeatures ++ directInteractionsEdgeFeatures
)

```

## Temporal Decay and Feature Engineering

To ensure the Interaction Graph remains responsive to recent behavior while retaining long-term context, the system applies exponential decay to historical aggregates. In [`src/scala/com/twitter/interaction_graph/scio/common/FeatureGeneratorUtil.scala`](https://github.com/twitter/the-algorithm/blob/main/src/scala/com/twitter/interaction_graph/scio/common/FeatureGeneratorUtil.scala), the methods `combineEdgeFeaturesWithDecay` and `combineVertexFeaturesWithDecay` merge yesterday’s graph with today’s snapshot using decay constants `α` and `1‑α`.

This yields a time-series-aware representation where fresh interactions carry higher weight, preventing the feedback loop from stagnating on stale signals:

```scala
// src/scala/com/twitter/interaction_graph/scio/agg_all/InteractionGraphAggregationJob.scala
val aggregatedActivityEdge = FeatureGeneratorUtil
  .combineEdgeFeaturesWithDecay(
    prevAggEdgeValid,                         // yesterday's edges
    aggregatedActivityEdgeDaily,               // today’s edges
    InteractionGraphScoringConfig.ONE_MINUS_ALPHA,
    InteractionGraphScoringConfig.ALPHA
  )

```

## Exporting to Real-Graph Features

Once aggregated, the graph is pruned to the most influential connections. `InteractionGraphAggregationTransform.getTopKTimelineFeatures` in [`src/scala/com/twitter/interaction_graph/scio/agg_all/InteractionGraphAggregationTransform.scala`](https://github.com/twitter/the-algorithm/blob/main/src/scala/com/twitter/interaction_graph/scio/agg_all/InteractionGraphAggregationTransform.scala) selects the top‑K inbound and outbound edges per user, converts them to the `RealGraphFeatures` Thrift format, and writes them to a versioned dataset.

Utility functions such as `GraphUtil.isFollow` assist in filtering edge types during this extraction:

```scala
// src/scala/com/twitter/interaction_graph/scio/agg_all/InteractionGraphAggregationTransform.scala
val realGraphFeatures = getTopKTimelineFeatures(
  aggregatedActivityScoredEdge,
  pipelineOptions.getMaxDestinationIds
)

```

## Powering the Recommendation Feedback Loop

The Interaction Graph closes the feedback loop by serving as the bridge between raw user behavior and model inference. Downstream services consume `RealGraphFeatures` through dedicated hydrators:

- **Home Mixer** uses [`RealGraphViewerAuthorFeatureHydrator.scala`](https://github.com/twitter/the-algorithm/blob/main/RealGraphViewerAuthorFeatureHydrator.scala) to enrich tweet-ranking requests with interaction history between the viewer and candidate authors.
- **Push Service** leverages [`PushTargetUserBuilder.scala`](https://github.com/twitter/the-algorithm/blob/main/PushTargetUserBuilder.scala) to incorporate real-graph signals into notification ranking.

The loop operates as follows:

1. **Signal → Graph:** User actions (likes, follows, dwell) are ingested into the Interaction Graph during the next nightly aggregation.
2. **Graph → Models:** Decayed, top‑K graph features hydrate ranking models (e.g., Home‑Mixer’s `ScoredTweetsPipeline`).
3. **Model → Surface:** Ranked candidates are presented to users.
4. **Surface → Signal:** New interactions are generated, re-entering the pipeline and closing the loop.

Because the graph applies temporal decay and filters low-signal edges, it balances responsiveness to recent trends with stability from long-term relationships—critical for preventing feedback loop collapse or echo chambers.

## Summary

- The **Interaction Graph** is the centralized ledger that aggregates billions of user signals into a time-weighted graph structure.
- **Scio jobs** under `src/scala/com/twitter/interaction_graph/scio/` handle daily ingestion of follows, retweets, likes, mentions, and dwell time.
- **Exponential decay** in `FeatureGeneratorUtil` ensures recent interactions dominate while historical context persists.
- **Top‑K pruning** converts dense graph data into `RealGraphFeatures` consumed by Home Mixer, Push Service, and other ranking pipelines.
- The architecture forms a **closed feedback loop**: user actions update the graph, graph features inform models, models generate recommendations, and recommendations drive new user actions.

## Frequently Asked Questions

### What types of interactions does the Interaction Graph track?

The Interaction Graph captures directed interactions including follows, retweets, likes, mentions, dwell time on tweets, address-book contacts, and flock activity. These are ingested from client-event logs and direct interaction streams, then aggregated into edge features such as `num_retweets` and vertex features such as `total_dwell_time`.

### How does the Interaction Graph handle stale or outdated interactions?

The system applies exponential decay to historical aggregates using constants `α` and `1‑α` in `FeatureGeneratorUtil.combineEdgeFeaturesWithDecay`. This weights recent interactions more heavily while preserving long-term signals, preventing the recommendation feedback loop from relying on outdated behavioral patterns.

### What is the relationship between the Interaction Graph and RealGraph features?

The Interaction Graph is the underlying data structure; RealGraph features are the production-ready extraction of that data. `InteractionGraphAggregationTransform.getTopKTimelineFeatures` selects the top‑K inbound and outbound edges per user from the Interaction Graph and converts them into the `RealGraphFeatures` Thrift format used by downstream ranking services.

### Which downstream services consume Interaction Graph data?

Multiple recommendation services ingest RealGraph features derived from the Interaction Graph, including Home Mixer (via [`RealGraphViewerAuthorFeatureHydrator.scala`](https://github.com/twitter/the-algorithm/blob/main/RealGraphViewerAuthorFeatureHydrator.scala) for tweet ranking), Push Service (via [`PushTargetUserBuilder.scala`](https://github.com/twitter/the-algorithm/blob/main/PushTargetUserBuilder.scala) for notification ranking), and Tweet Mixer. These services hydrate ranking models with interaction history to personalize content surfaces.