Tl;dr: This blog post is the first in an upcoming series that will explain how Coinbase defines and measures our mobile app quality and how we have improved it in the past 12 months. Quality at Coinbase is composed of three main facets: performance, reliability, and culture. The following post focuses on performance and outlines the three-pronged strategy to make the app faster.
In early 2022, a year after migrating the Coinbase mobile application to React Native the engineering team was shipping new features at increasing velocity across both platforms. Though React Native enables faster iterations across iOS and Android platforms, React Native apps can suffer from performance bottlenecks that native apps typically do not – due to the JavaScript bridge required to compile cross-platform.
Seemingly minor regressions of a few milliseconds to transitions or loading times are difficult to observe, but cause noticeably slow experiences for users as they compound. Without guardrails around performance, latency was increasing as we added more features, and this reflected a growing need for change.
Coinbase prioritized an initiative to define, measure, and improve app quality to reduce latency. We defined high-level quality metrics around performance, reliability, and bug resolution to ultimately improve the user experience.
“If you can’t measure it, you can’t improve it”
Overall app performance is the result of many inputs. Which of these inputs impacts latency is difficult to pinpoint without the ability to measure a starting point and subsequent progress. For our first phase of app improvement, we defined objective North star metrics to measure performance and then created guardrails to maintain progress.
There are many metrics that can be used to measure performance of mobile applications (navigation total blocking time (NTBT), app render complete (ARC), or frame rate) which provides a holistic view of the experience for users. At Coinbase, we decided to focus most heavily on optimizing what we deem “critical user journeys” (CUJs) — a flow of the most common, critical steps a user takes to complete tasks.
The buy flow, pictured above, was chosen as the primary flow (CUJ) to optimize as these screens are highly trafficked across the Coinbase app. Several of the steps involved such as “Home Tab” or “Asset Detail Screen”, are shared by many user journeys — so improving performance of those screens would have the most impact to users.
We track the latency of “user steps” or screen loading times — the timing of which begins once the user navigates to the screen and ends when the user interface (UI) on the following screen renders and the user can continue on their critical path. The timings measured by these events are compiled and sent to our analytics service that subsequently make them available for consumption and analysis.
Once the data is available and has been stabilized, monitoring becomes available via a performance dashboard which compiles and condenses the user steps tracked throughout the application. A high utility dashboard around performance provides an at-a-glance summary of how critical journeys are operating via numerical and graphical widgets consumable by engineers, stakeholders, and leadership. Additionally, aggregating relevant data on services – say network request data – that contribute to measured performance can assist in efficient root cause analysis should a regression occur.
Real time data allows us to set thresholds and create alerts for anomalous changes to expected user journey time. Product teams own their respective journeys, are empowered to hold other teams accountable for services they consume, and are ultimately responsible for maintaining service level objectives (SLOs).
With baseline performance metrics in place, unearthing and selecting paths forward to latency reduction becomes easier. In a form of “working backwards”, we set latency goals for each relevant journey. By knowing the “destination” number, and the delta with the current experience, technical leads were able to deliberate on best approaches to close the gap.
Realtime data provides a guardrail to gauge progress as experimental changes rolled out, quickly determining changes that worked and those that did not which allowed for quick iteration. The team then settled upon strategies outlined in subsequent sections which include: frontend code optimizations, speeding up backend services, and rethinking the product.
Rearchitect Client Trading Funnel
The trading funnel for buying an asset on the Coinbase app consists of a trade input screen, a preview screen, and a confirmation screen. Though just three screens, it contributed an outsized proportion of latency to the overall critical user journey. The following patterns were some of the most impactful in providing an optimized user experience (UX).
Optimistic Fetching
Sending network requests will always impose some latency cost independent of how fast a response may be. The fastest request is not having to make one at all.
Coinbase uses a combination of graphql (GQL) and Relay for data fetching while GQL serves as the entrance into the graph layer. The graph layer fits in between the UI layer and the service layer and brings Coinbase data and services together into one consistent, secure, and discoverable interface for access through a single endpoint.
Relay prefers a concept called preloaded queries for data fetching as it can kick off the request for data prior to a component being rendered or the user request. This is a powerful tool, but must be wielded carefully as over-fetching data and subsequent taxing of backend resources is a real concern for applications at scale.
A funnel like the one shown above is the ideal place to enact optimistic fetching. The user is being progressed forward screen by screen in a synchronous, expected manner. Given funnel conversion rates by step one can calculate tradeoffs of sending x% more “empty” (never used) requests to the backend in favor of instant transitions between screens. For the purpose of the Coinbase trading funnel the input -> preview screen transition is a suitable use case for preloading to reduce friction for users attempting to complete a trade. The input screen, alternatively, ended up being a poor use case for preloading given the diverse, numerous quantity of entry points around the application.
Thoughtful Suspense
One of the most powerful features within Relay is how it leverages React suspense. When utilized properly in conjunction with the local Relay cache, it allows for localized loading states for certain elements on a view that is actively fetching data while showing others to the user. An ideal pattern will wrap components that have their own fragment in a suspense boundary so if the data is not available in the local cache the loading state does not bubble up to the screen level. As usage of GQL and Relay have matured internally, the UX and metrics around CUJs have significantly improved. Most of the view remains interactable for the user while portions of it will render as data becomes available.
Speed Up Backend Services
With user journey metrics in place, the final missing piece of our tool box is improved introspection to how our backend services are performing as well as their impact on user journeys. Migrating to use a combination of GQL and Relay for client data consumption meant rethinking how we monitor performance of specific fields making up a larger GQL request. Due to the way standard Relay composition uses a single “root query” it becomes easier to create dashboards that track overall query performance as well as the latency for individual request fields against specific user steps. With objective data comes the ability to prioritize which backend services should be optimized to make the most impact to end users. For example, acutely sluggish fields on a large root GQL request prevent an optimized experience, and are the highest priority to fix.
Product Iteration
For Coinbase, providing stable experiences on functionality pertaining to a user’s assets and iterating quickly is of utmost importance in maintaining trust.. Businesses that stagnate in a fast moving space like crypto tend to fall behind.
The foundation of a user experience is the design of the product feature. It can be impactful to rethink assumptions that were made when a feature was originally developed. Here are two examples where we iterated and redesigned the technology that powered a flow in order to have a more performant experience.
Polling Data
Historically, Coinbase’s trading interface has been comprised of two primary actions:
“Creating a quote” for a trade that returns information to the client about the trade a user will perform based on payment methods, asset to trade, fees, etc.
“Committing a trade” to inform the backend that based on the quote created in step one we would like to kick off a trade.
Quotes were created when an input changed – say when the amount to trade is updated by the user or when the user transitions to the preview screen. We decided to apply polling for quotes on both the input and preview screen which had numerous beneficial impacts including:
Providing live updates to users as asset prices changed
Eliminating a blocking request for a quote while transitioning between input and preview screen
Eliminating an issue where quotes could expire on users within the funnel if they stepped away
Async Trade
There are multiple queries that must succeed between when a user clicks the submit trade button and when they arrive at the trade confirmation screen in a successful state. This step accounted for the majority of the latency of the buy flow.
By eliminating the requirement for trades to fully process to a completed state before allowing the user to leave the funnel, we were able to make significant improvements. In this new paradigm, the backend would receive the commit trade request, inform the client it had been received, and finally the client would inform the user their trade is submitted while the user receives subsequent notifications when the trade has successfully resolved. This relatively simple shift in how this confirmation step functioned significantly reduced the step’s latency — giving our users a much faster and streamlined experience.
Maintaining and optimizing App quality demands time and persistent iteration. Investing in a performant app experience yields tangible benefits - A/B tests that compared the same flow with one version faster than the other showed that latency improvements are correlated with higher revenue.
Looking back at the culmination of the changes to numerous codebases and services over the course of a year, it is clear that a focus on quality provides a huge impact for both end users and business goals. The buy, sell, and convert funnels saw an 80% improvement from initial performance measurements to today.
Coinbase achieved ambitious targets for user journeys through observability, goal alignment, and taking a three-pronged approach to optimization: improving client architecture, making backend services faster, and product innovation. Performance excellence is now even more ingrained in our culture, driving every product change and feature launch. We are proud of the updates we have made to maintain a world class React Native app and we will continue to do the work necessary to continue to improve the experience for our users.
About Jacqueline Teran and Brendan Lynch
Engineering Manager
Staff Software Engineer
Security,
Jan 16, 2025