This article is the third in a series covering how Uber’s mobile engineering team developed the newest version of our driver app, codenamed Carbon, a core component of our ridesharing business. Among other new features, the app lets our population of over three million driver-partners find fares, get directions, and track their earnings. We began designing the new app in conjunction with feedback from our driver-partners in 2017, and began rolling it out for production in September 2018.
The competition between urban architecture and wireless data technology means lapses in coverage—dark spots in cities where our phones won’t work. Driving through urban landscapes means finding more of these dark spots, leading to frequent changes in network quality and levels of congestion. These lapses in coverage particularly affect Uber’s driver-partners as they attempt to pick up or drop off riders.
The pain points here can be demonstrated best by an example. Suppose a driver finishes a trip at a crowded airport in Bangalore. The rider wants to pay with cash, and the driver needs to complete the trip in the app to see the final fare. Pulling up to the curb at the airport, the driver’s phone can’t connect to the Internet. The rider is rushed to make their flight, but the lack of a connection means the driver can’t complete the trip in the app and get the final cost. The driver might drive further down the terminal, taking extra time, potentially extending the trip, and causing frustration for both rider and driver.
To deal with lapses in network coverage and prevent these types of scenarios from occurring, we came up with Optimistic Mode. This new feature for our driver app lets the app work offline so that a driver can end a trip even without a connection and retrieve the last price estimate received from the server. Optimistic Mode allows the app to work regardless of network conditions, leading to more positive trip experiences for rider and driver alike.
Optimistic Mode components
We supported some offline capability in the previous driver app by collecting failed requests and batching them to the server to be consolidated once connectivity was regained. While this feature helped prevent some errors from being displayed, it wasn’t able to intelligently update the state of the application, stack multiple actions on top of each other, and persist state across sessions. We developed the components described below for our new driver app to deal with these issues.
Optimistic requests
Any component of the driver app capable of operating optimistically begins the flow by submitting an optimistic request. An optimistic request has the ability to serialize and deserialize to disk, very similar to a regular network request, and every optimistic request is paired with an optimistic transform.
Optimistic transforms
The main component that allows Optimistic Mode to work are called transforms, in other words, operations that transform the current state of an object to an optimistic state, i.e., the expected state to be returned by the network. Transforms can also be stacked, applying their changes in order as an object passes through each transform. To understand transforms with a simple example, let’s imagine a class “Counter” which has a property “count.” We can then implement a transform which increments the count property of the Counter object.

Transforms can be as simple or complex as needed for our optimistic operations. Each optimistic request has a transform associated with it. The transform outputs an optimistic state that matches the eventual response from the optimistic request. This way, the user will not notice any change in the app when the response comes back from the network, providing a smooth transition.
When an optimistic request is submitted to the client, the transform associated with the request is applied immediately to move the app into an optimistic state, making it appear that the request has completed. The optimistic state outputted from the transform will be maintained until a response from the server is received with the actual state, syncing app and server.


Optimistic stream
We use RX streams as the message bus for data to be passed through the app. Every feature in the app reacts to the state changes that are published on the datastream. This mechanism enables us to use the same stream to easily apply optimistic transforms to the latest state of the object. To obtain the optimistic state, we combine the last known state of the data on the stream with the available transforms for the data. The data has each transform applied to it before being published back on the stream and consumed by the feature. The feature then simply reacts to the optimistic state of the data.
Dependent requests
There are also requests that are dependent on optimistic requests completing. For example, it wouldn’t make sense to send a request to end a trip that the backend doesn’t even know has started. Such dependent requests will be queued for a period of time while we wait for the optimistic requests to complete. If this period is too long, we fail the request, notifying the user with a network error message.
Design challenges
We faced several challenges in this design. We wanted to support stacking optimistic requests, allowing for multiple steps to be completed without a network connection. Due to being out of sync with the server we also needed to handle cases where we incorrectly moved into an optimistic state and must revert to a previous state. Ensuring that we show the driver the most accurate state reliably is something that took several iterations and will continue to be optimized as we move forward.
Rebasing transforms
With Optimistic Mode enabled, the application may receive other network data before the optimistic request has been able to complete.
