Performance as a growth requirement

Threads’ launch in 2023 brought 100 million users in five days, and the app has since grown past 300 million monthly international users. The engineering team has scaled along with it, from a small group to more than a hundred contributors. With that growth came new features: European expansion, Fediverse integration, a public API, and new discovery and sharing tools. None of that matters if the app itself isn’t reliable and responsive.

The Threads iOS team tracks performance through a set of metrics that cover distinct parts of the user experience. These aren’t just internal numbers; they’re designed to catch regressions early, whether the problem affects a large share of users or only a small, but still significant, percentage.

%FIRE: catching slow image loads

Photos are a core part of Threads, and slow or broken image rendering drives people away. The team monitors the percentage of users who experience a “frustrating image-render experience,” or %FIRE. The metric is computed from rendering data as shown below.

Figure 1: %FIRE calculation.

%FIRE is sensitive to both client-side and backend regressions, but it doesn’t cover every image bug. Earlier this year, profile photos flickered on iOS due to how view models were compared during reuse. That was frustrating, but it wouldn’t have shown up in %FIRE.

Time-to-network content (TTNC)

App launch speed and feed delivery speed are grouped under time-to-network content, or TTNC. The measure tracks how quickly the app can load a fresh, personalized feed — not just content pulled from cache. Slow launch screens or lengthy loading spinners cause users to exit.

Keeping the binary small is a key part of launch performance. Every commit to Threads is checked against a binary size threshold, and code that would push past it isn’t allowed to merge. The team has also removed unnecessary code and assets since launch, leaving the binary at one-quarter the size of Instagram’s. As a side benefit, non-incremental builds for Threads compile twice as fast as Instagram’s.

Creation-publish success rate (cPSR)

cPSR measures how often posts succeed, tracked separately for text, photos, and video. Publishing media is more failure-prone than text because photos and videos are transcoded locally and require larger uploads. Users may also background the app right after tapping “Post,” which on iOS can terminate the upload process within seconds.

Prioritizing navigation latency

Navigation latency affects almost everything a user does after launch — tapping a push notification, opening a post from the feed, or moving to a new surface. Improving it requires knowing where the pain points are, which the team determined through a deliberately engineered experiment.

Learning from a boundary test

Early in 2024, the performance team introduced a small amount of artificial latency for a subset of users navigating to profiles, post permalinks, and the activity feed. This “boundary test” let them extrapolate the impact of improving content delivery to those surfaces.

Latency injection Daily Active Users Foreground sessions Likes Conversation views
Activity: 0.12s
Conversation: 0.29s
Profile: 0.28s
In-app navigation
Activity: 0.15s
Conversation: 0.36s
Profile: 0.35s
-0.68%
Activity: 0.19s
Conversation: 0.54s
Profile: 0.53s
-0.54% -0.81%
Activity: 0.12s
Conversation: 0.29s
Profile: 0.28s
App launch -0.37% -0.67% -1.63%
Activity: 0.15s
Conversation: 0.36s
Profile: 0.35s
-0.67% -2.55%
Activity: 0.19s
Conversation: 0.54s
Profile: 0.53s
-0.52% -0.65%

The results were clear. iOS users have little tolerance for added latency; increases reduced how often users launched the app and how long they stayed. The smallest injection had negligible impact on some views, but the largest caused broad negative effects across reads, posts, and general interaction — all without touching the core feed.

Measuring latency with SLATE

The SLATE debugger.

Consistently measuring navigation latency across a large app is hard. Different surfaces, content types, and terminal states such as errors or empty views require a uniform way to start and stop timing. That’s why the team built SLATE, the “Systemic LATEncy” logger.

SLATE observes navigation-triggering events across the UI lifecycle: when views are built, spinners and shimmers appear, network content lands, and errors surface. It works by marking specific events in code, but crucially, it auto-creates markers when developers use the common UI components. That makes it scalable across the large Threads and Instagram code bases, so engineers can enable a debugger in internal builds and build dashboards to see production behavior for their features without manual instrumentation.

GraphQL adoption, validated by SLATE

Threads and Instagram have historically used REST for network requests. Threads for Web, built later, runs on GraphQL, leaving iOS and Android to support both protocols whenever a feature ships to both platforms. The team wanted to consolidate on GraphQL but needed to verify it wouldn’t add latency during parsing and data handling.

A test was run on user list views, such as Followers and Following. The existing API was abstracted into a Swift protocol, with factory methods supplying either REST or GraphQL providers. SLATE markers then compared end-to-end latency between the two implementations.

Figure 2: Latency (p95) loading Following and Followers lists via REST and GraphQL.

The p95 latency for viewing a followers list was nearly identical between REST and GraphQL, as were results across other views. That data gave the team confidence to adopt GraphQL for all new endpoints, and SLATE provided the evidence needed to make the switch.

Drafts: building resilience for a core action

In early 2024, Threads added basic draft saving on iOS and Android. Beyond being a top user request, Drafts was designed to address a pattern seen in user-submitted bug reports: the most common posting complaint was simply not being able to post, often without a clear reason why. The team theorized that giving people a way to persist their content would reduce frustration even if it wouldn't directly move the core publish success rate (cPSR) metric for single-session posting.

To validate the feature, Threads released Drafts to a small cohort and compared their subsequent bug reports against those from users without the feature. The result: 26 percent fewer people submitted posting-related bug reports when they had Drafts. A follow-up improvement added a "save as draft" option to the failure dialog shown when a network issue prevented a post from sending, instead of forcing a choice between retrying or discarding the post entirely.

The real-world test came unexpectedly when an outage took down Threads for a short period. Drafts usage spiked sharply during the incident, confirming that people were using the safety net when they hit serious problems.

Figure 3: A spike in Drafts usage during a brief outage.

The storage cost of saving media

After the public release, Threads discovered the Drafts feature had an unfortunate side effect: average disk usage was climbing dramatically. User complaints reported the app consuming many gigabytes of storage. The root cause was media handling in the Composer. Threads uses PHPickerViewController to power its photo and video gallery picker.

That component conveniently runs out of process and gives users granular control over which media an app can access. However, the URL it delivers for a selected image grants only temporary access. Between app sessions, Threads could lose permission to read an image attached to a draft. If a user deleted the photo from their gallery, it would vanish from the draft as well. The fix was to copy selected photos and videos into an application-container area dedicated to Drafts.

Figure 4: Disk usage in Threads after Drafts launched.

Unfortunately, that copied media wasn't being fully cleaned up, so storage use grew over time—in some cases dramatically. Fixing the cleanup yielded gains beyond storage recovery: app launch got faster (-0.35%), daily active users grew (+0.21%), and original content posts rose (+0.76%).

Threads’ Posted toast.

Making text posts feel instant

Having already studied navigation latency, the performance team knew text replies were worth optimizing. Along with reducing absolute latency, they decided to attack perceived latency with a server-side change. A new feature in the network stack lets the server notify the client when a posting request is fully received, before it has been processed and published. Since most failures occur between the mobile client and the servers, a successfully received request is very likely to succeed.

With that acknowledgment callback, the iOS client can show the "Posted" toast as soon as the request is received, even though the backend hasn't finished creating the post. Text posts appear to publish faster, and the app feels more conversational.

Swift Concurrency for race-free publishing

Moving publishing from a synchronous to an asynchronous model surfaced race conditions. The asynchronous transcoding step, plus new ones around upload task management and media metadata, occasionally produced malformed payloads in analytics. Rare edge cases at scale can hurt performance metrics and user experience.

Threads' codebase is mostly Swift, but some publishing code was still in Objective-C. Swift's data-race protections and type safety made it the right target for migration. Teams across Meta are adopting Swift's "complete concurrency" in preparation for Swift 6. Threads has been migrating older Swift code and writing new frameworks with complete concurrency from the start—what the team describes as the biggest change to iOS development since Automatic Reference Counting. Swift's strict concurrency model prevents the data races that were causing problems with Threads' optimistic uploader.

What's next for the iOS app

As Threads scales, the team will keep monitoring established metrics like %FIRE, TTNC, and cPSR while adding features. Work is ongoing to make post delivery faster with fewer loading indicators, modernize Swift usage for stability and faster builds, and refine tools like SLATE for debugging and regression testing.

Users can contribute directly, too. Bug reports helped identify the areas that led to Drafts and verified it solved real frustrations. In Threads and Instagram, long-pressing the Home tab or shaking the phone submits a report—and the team reads them.