Amy is outside a busy supermarket, where she has purchased essential food supplies during the shelter-in-place order. She’s waiting for a Lyft ride home, which was requested by her friend, Deb, who wants to help cover her costs. Her driver, Marc, is a few blocks away. He attempts to call the rider, Amy, to ensure she is ready outside. However, Deb, the ride requester receives the call. After a game of phone tag between all three of them: Marc confirms with Deb that Amy is ready outside. He tries to identify Amy among the sea of people waiting for rides, but the name and the photo in the app is Deb’s. Drivers are lining up behind Marc while he looks for her, and he is being pressured to leave the pickup area.
As Lyft has continued to grow, we have observed more riders unofficially requesting rides for friends and family. This use case became apparent when analytics revealed rides where the pickup or drop-off locations differ drastically from the requester’s location.
It became clear that we needed to officially support this experience to solve the identity problem, improve the pickup experience, and most importantly enhance the safety of our riders and drivers.
That’s why we built the ability for riders to request rides for their friends. This post discusses the challenges we encountered while building this experience into the Lyft apps and how we addressed them: developing the user experience; managing the ripple effect of a new ride request feature across teams within the company; and ensuring a reliable rollout during COVID-19.
The ability for the rider to be able to see the ride details and track the driver is an important part of ensuring a smooth experience. When the requester requests a ride for a friend or family member, we want to allow both the requester and the rider to be able to monitor and control the ride synchronously in their own app.
Allowing a ride to be synchronized between the requester’s and rider’s apps was non-trivial:
- There was an existing assumption that the ride has one user who performs three roles: requesting, paying for, and taking the ride. The requester and the payer are the same, but the rider is now different. We needed to break this assumption, while allowing existing functionality to still work for the rider.
- We changed the way the app fetches active rides for the requester, adding rides that the requester has requested for friends. This allows us to show the ride in the requester’s app as well as in the rider’s app. We had to add new secondary indexes into our databases so that we could query these new active rides without affecting performance.
- We modified server-side code that controlled which actions are allowed or what UI components should be rendered on the app for the requester and the rider. Only the requester should be able to add tips, but only the rider should be able to rate the driver and exchange text messages and/or calls with the driver.
To build the feature, we had to change screens throughout the ride request flow. We needed requesters to be able to select another rider on the pickup and drop-off selection screen. We also needed to update the rate and pay screen to prevent the requester from rating the driver, and prevent the rider from tipping the driver.
These screens were owned by different teams, so we had to collaborate to ensure there were no conflicts with other projects being worked on in parallel, and no negative consequences of our changes.
Building the functionality on the server side required modifying 21 microservices across 16 teams. Fortunately, there is a high level of standardization in how they are built. Microservices at Lyft are built in Python and Go. Each codebase has the same overall directory structure, and uses largely the same libraries to provide APIs. A Lyft microservice can be launched in a development environment without any specific knowledge of how it works. APIs are configured in a single repository, and automatically generates code for the iOS and Android codebases to consume. Most importantly, engineers across other teams are supportive and welcome contributions from other teams.
Our collaboration extended well beyond engineering. Some of the teams we worked with across the company include: integrity, legal, data science, data engineering, design, UX research, marketing, customer support, and communications. We had to think through what would happen when we break the assumption that the requester is always the rider:
- Growth accounting and user reporting: we created a new scenario where the requester are different users, requiring us to re-evaluate existing reporting.
- Fraud: we had to think through how this would impact our fraud protections, and what new fraud vectors might we introduce. We revisited existing protection mechanisms and made updates so that those are evaluated with the correct user (requester or rider).
- Legal: we needed to ensure that this feature complied with applicable privacy laws as well as our privacy practices.
- Support: our support agents had to handle new types of support tickets. We had to update our support tools to show the requester and rider were different, and train support agents to handle these new cases. For example: only the requester who paid for the ride should be able to receive concessions from a payment issue.
- Experimentation: existing rider metrics were derived from the rider’s perspective. We had to add new metrics for the requester’s perspective, and ensure that any effects with existing metrics were understood.
Answering these questions required a concerted effort across the company.
As shelter-in-place orders have taken effect across the U.S. during COVID-19, the stakes for safe and reliable rides, as well as the stability of our apps have become more important than ever. Getting riders to doctors for appointments or to grocery stores for essential food supplies are critical during this time.
At the same time, the reduced traffic presented obstacles to our typical testing for shipping a new feature.
We needed to roll out the feature much more slowly and monitor it more carefully, in case there were unforeseen negative effects. It was crucial that we deploy the new feature without causing any bugs or crashes in the app during the ride request flow. Key to this stability was a clean isolation of the feature’s components from critical code paths, and a high degree of testability. We also needed a reliable way to disable the feature in production in case something did go wrong.
To achieve all of this, we built the feature with a unidirectional-flow oriented architecture. The rider selection component manages its own state: the selected rider ID. Once the requester has selected another rider: the selected rider’s user ID is sent to the ride request handling logic, which injects the rider ID into the ride request, and therefore lets the ride object be created with the correct payer, requester, and rider. The architecture includes safety flags by default, so the component can be easily disabled and hidden if it causes any issues.
Stay tuned on the Lyft engineering blog for a future blog post covering the architecture in detail.
While building for the rider’s experience, one challenge we encountered was handling riders whose apps are on older app versions. We do not force riders to upgrade their apps due to the high friction experience of having to re-download the app.
We had to carefully adjust the existing APIs to enable the new functionality. This was feasible with existing server controls that we could leverage to toggle the visibility of the rating and tipping components, as well as showing an error if they try to update their destination. If the rider does not have the Lyft app installed, they are able to view a web based view of the ride: we were able to re-use our existing product that allows sharing ride details with others for this functionality.
As Lyft has grown, it has become increasingly important to think ahead about what we may build next. When we build features to launch today, we must also be sure we are considering potential future use cases, so we pave the path for future engineers.
The server controls were fortunately put in place by engineers who were designing for the future. In our case, we focused on building this product by creating a ride object with payer, requester, and rider all mapped to the correct users. This allows us to not only provide the ability to request rides for friends and family, but also prepare for future functionalities such as:
- Requesting a ride for multiple riders at the same time.
- Allowing the requester to follow one or more rider’s ride that they requested for.
- Enabling seamless communication between the ride requester, the rider, and the driver.
There is more work to be done, but we hope that applying forward-thinking will help to pave the way for future work.
Try out requesting a ride for a friend today in the iOS and Android Lyft apps. If you are interested in working on these problems, come join our team and help us to improve people’s lives with the world’s best transportation: check out our careers page!
Thanks to the following people who played critical parts in building this product (in alphabetical order): Adam Cmiel, Ajay Andrews, Akshay Patil, Alex Dailida, Alice Zhang, Alison Marlin, Amanda Schroder, Amy Schultz, Andrei Stasevici, Anton Tananaev, Ashwin Raj, Bomee Park, Brad Ellis, Brady Law, Brian Ng, Byron Wilkes , Caitlin Osbahr, Carolyn Buehler, Chris Martin, Chuanxin Hu, Cooper Smith, Daniel Sumstine, David Hildebrand, David Kwan, Delphina Yuen, Denis Nekliudov, Elias Ramirez, Eunice Joung, Gabriel Lanata, Gerald Lee, Glen Robertson, Gonzalo Larralde, Harita Yenisetty, Harith Khawaja, JP Simard, Jaden Choi, Jason Bridgemohansingh, Jeff Hurray, Julio Carrettoni, Kang Lee, Kyo Kim, Lauren Frederick, Leo Jiang, Liuyin Cheng, Marcel Ortiz, Melissa Hamilton, Mengying Yang, Meredith Guo, Michael Lodick, Morgan Holland, Neil Shah, Nick Ung, Patrick Barry, Preet Anand, Rebecca Shields, Reza Mostafavi, Robin Chou, Scott Berrevoets, Sean Shi, Shawn Shaligram, Shivendra Kishor, Stephanie Carpenter, Suresh Pragada, Tak Cho, Tory Nelson, Xiang Long, Xinwei Gao, Yvonne Wong