Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Former Uber engineer/EM here: I worked on the Rider app.

The “there are only a few screens” is not true. The app works in 60+ countries, with features shipped in the app that often for a country, and - in rare cases - a city.

The app has thousands of scenarios. It speaks to good design that each user thinks the user is there to support their 5 use cases, not showing all the other use cases (that are often regional or just not relevant to the type if user - like business traveler use cases).

Uber builds and experiments with custom features all the time. An experimental screen built for London, UK would be part of the app. Multiply this by the 40-50 product teams building various features and experiments outside the core flows you are talking about (which core flows are slightly different per region as well).

I worked on payments, and this is what screens and components are in the Uber app:

- Credit cards (yes, this is only a a few screens)

- Apple Pay / Google Pay on respective platforms

- PayPal (SDK)

- Venmo (SDK)

- PayTM (15+ screens)

- Special screens for India credit cards and 2FA, EU credit cards and SCA, Brazil combo cards and custom logic

- Cash (several touch points)

- AMEX rewards and other credit card rewards (several screens)

- Uber credits & top-ups (several screens)

- UPI SDK (India)

- We used to have Campus Cards (10 screens), Airtel Money (5), Alipay (a few more), Google Wallet (a few) and I other payment methods I forget about. All with native screens. Still with me? This was just payments. The part where most people assume “oh, it’s just a credit card screen”. Or people in India assume “oh it’s just UPI and PayTM”. Or people in Mexico “oh, it’s just cash”. And so on.

Then you have other features that have their own business logic and similar depths behind the scenes when you need to make them work for 60 countries: - Airport pickup (lots of specific rules per region)

- Scheduled rides

- Commmuter card functionality

- Product types (there are SO many of these with special UI, from disabled vehicles, vans, mass transport in a few regions etc)

- Uber for Business (LOTS of touchpoints)

- On-trip experience business logic

- Pickup special cases

- Safety toolkit (have you seen it? Very neat features!)

- Receipts

- Custom fraud features for certain regions

- Customer support flows

- Regional business logic: growth features for the like of India, Brazil and other regions.

- Uber Eats touchpoints

- Uber Family

- Jump / Lime integrations (you can get bikes / scooters through the app)

- Transit functionality (seen it?)

- A bunch of others I won’t know about.

Much of the app “bloat” has to do with how business logic and screens need to be bundled in the binary, even if they are for another region. E.g. the UPI and PayTM SDKs were part of the app, despite only being used for India. Uber Transit was in a city or two when it launched, but it also shipped worldwide.

And then you have the binary size bloat with Swift that OP takes about.



I also worked for Uber but on backend systems in the payments flow. Every single piece of app functionality mentioned above has at least one, if not multiple, backend systems and teams supporting it.

Like in the app, "payments" sounds like it would be a simple Stripe integration but it isn't. For one, that's way too expensive at Uber's scale. They build their own payment integrations to save fractions of a point that adds up to tens of millions of dollars in saved expenses.

There's real time systems to facilitate the trip, then the order processing systems to process that raw data, hydrate it, store it in multiple data warehouses, and publish it to Kafka to be consumed by literally hundreds of other services. The main money pipeline involved many services covering things such as rider incentives, driver incentives, rider fraud, driver fraud, tax calculations, rider payments across all of those aforementioned payment methods, receipts, invoices, driver disbursements, rider support, driver support, and more.

Then take in to account that almost all of those services were written for a world designed only for taking passengers from point A to point B with Uber Black but have huge added bloat to accommodate:

* Uber X

* Uber Pool

* Uber Eats

* Uber Freight

* Uber Moto

* Lime

* Jump

and proof of concept work for all sorts of new business ventures like Uber Elevate and ATG. All of that cruft is expensive to maintain and all of those actual product lines started as experiments.

When I left in 2019, there were two versions of the vast majority of those systems in production as all were being rewritten to handle the significant changes to Uber's business model from inception to now.

Additional edit: These services also require massive numbers of server hosts. All of the old stuff was written in Python, all the new stuff is in Go. That alone is saving Uber tens of millions per year just in hosting costs.


It genuinely blows my mind anyone who has written software couldn't have guessed at all of this from the beginning.

Uber PMs and engineers should be super proud. The fact that each user thinks that their one use-case is "all the app does" is fucking brilliant product design.

I've had over a thousand Uber rides (used it to commute) in 10+ countries and it's obvious there the way it seamlessly adjusts to each region but even otherwise I think I could have guessed just from the difference between SF and Seattle. There was a sort of joy of opening the app in a new region and finding unique options. Some sort of (maybe intentional) superstar-level product design.

Rickshaws in Seattle? Cash payment in India? Uber Taxi in SF? Sweet!


> The fact that each user thinks that their one use-case is "all the app does" is fucking brilliant product design.

Alternatively: The fact that they try to bundle all the world's use cases in some 100+ MB monstrosity of a client app is a tragicomic commentary on the misaligned incentives in the tech world's VC-driven wannabe-monopolies.


Anything else seems like an even bigger mess:

What happens when a user who travels unexpectedly discovers they need to download a new version of the app for where they are now? And what if they're on a low-bandwidth or capped cell plan and can't download a 100MB app easily, where 3/4 of the data is duplicates of stuff in their other 5 copies of the app?

What happens when a user tries to book an Uber from one area with a special app version to another area with a different special app version? This could easily generate tens of thousands of permutations.

What happens if the user is physically in one country right now, but is a national of another country and wants to pay for their Uber in their usual currency and payment method?

If the user ends up needing to have 20 copies of the app for various locations, how well will they sync ride history, changes in payment methods, and other such info?

I don't even work for Uber, and I thought of all of these complications in 10 minutes. The real Uber probably has 100x more complications that would arise from splitting location-specific functionality into location-specific apps.


Initial install bundle should be minimal and only have code that is required everywhere and or for downloading/installing more stuff.

It's not about creating 10,000 different permutations of an app, but downloading/installing content on demand.

If we are afraid that there might be problem that user later on has not enough bandwidth or issues like that, we'll start immediately downloading/installing after initial bundle in order we think this user might need them.

This means much faster initial usage, much less bandwidth used in total.

Same with developing experience. You shouldn't have to compile code that is not important for your development exp. This way compiling wouldn't take much time, dev productivity would be increased tremendously.


You're not always allowed to do that. E.g. Apple requires all code to either be in the bundle or run in a webview (and even then you risk removal if you introduce "significant" features): https://developer.apple.com/app-store/review/guidelines/#2.5...

For Android purposes: maybe it's allowed in the Play store? I'm not sure tbh. There are certainly other stores with other rules (and Uber is in many of them too), or they could distribute it themselves (but we can see how well that has worked for e.g. Fortnite), but you still need to handle any scenarios where it's required.


Sounds like base things, common for everything should be done using native app code and everything that varies that much should be made as PWA in WebView. You can download this PWA in the background too once app has installed if you are afraid you might not be able to do this on demand. I don't see why not use PWA? The performance implications in this case are minimal imo. And you have all the other fully fledged native features since your app is combination of both. Main arguments against PWA is that iOS doesn't support that well, but hell in this case you skip ALL problems with PWA since you have full capabilities already thanks to being generally native and you are downloading it from AppStore anyway.

In this case it could easily be reused within both Android and iOS without having to create duplicate business logic which I think already is a nightmare.

Airports logic I think should still be something that is defined using JSON/XML or w/e and algorithms should handle parsing that.

Or come on, there must be a way to get airports logic in an easier way. Does user have to update the app every-time when there's some minor airport update?


PWA won't give you access to random device APIs that you didn't open up for a specific use before needing them, which e.g. cameras, payment systems, etc often need. All that still needs to be native, and that can be substantial in some cases.

If it does, say you made a generic shim, you're effectively downloading and executing dynamic code. Maybe not by a technical definition, but that's not the point of Apple's rule, nor will they care about technical arguments when they reject it. There are plenty of examples of this happening in practice, I'm not talking hypothetically - it's why they have a carve-out explicitly for education apps. Even if you don't do stuff like that, and stay entirely in the webview, PWAs (unless it's actually being run in the browser, of course) are not allowed to make significant changes without going back through review.

edit: you can absolutely shrink your binary size with a PWA or similar though, yeah. There will definitely be other tradeoffs you have to make though, I'm not deeply familiar with those. Performance and integration with native libraries at the very least (e.g. map rendering).


There's also something to be said about supporting 60+ versions of a client...


Not following that thoughtline?


By downloading dynamic code, in effect you're running N (or some combinatorial number of) different apps.

Granted, you can/should strongly isolate them so it's infeasible for one module to interfere with another. But as always, YMMV in practice. And good luck testing them all either way.


No, that's why it's beautiful product design. They know something about me. I am at location A and I have a problem: I want to be at B. They then solve that problem smoothly with reasonable defaults. They know this is the prime problem I have and that I have some top few variants on this.

Speaking of the defaults, the auto-suggests these guys have are fucking ridiculous too. Both Lyft and Uber. It's 4 AM in the morning and I've opened the app at home: offers me a ride to SFO. It's 11 PM in the evening on a Friday/Saturday and it offers me a ride to my friend's place. It's midnight and I'm at that friend's? They offer me a ride to my favourite club. I am one-click touching everywhere. These guys are good.

Superb user functionality.


If anyone has a good reason to bundle every location’s use case into one app it’s Uber. A common Uber user scenario is someone who takes an Uber to the airport in one country, arrives in another and has poor internet capability for one of several reasons (roaming, poor mobile service, must connect to airport wifi, etc).


What solution do you imagine that isn't a worse user experience? I can think of lots of obvious ways to simplify the client, but they all seem likely to make things worse for me as a user in pretty predictable ways.


Thank you for all these details. I think it's a good sign when people think your app is simpler than it really is - the Uber app does a good job of hiding this complexity.


I had a theory about why very very skilled people aren't promotted as they deserve : because they make hard problems look simple.

On the other hand, the not so skilled ones...


thanks for sharing. I'm a former Uber engineer still reading in most Uber-related reddit threads how they could build the app in a weekend


There needs to be a game of Uber HN/Reddit thread BINGO.

Squares include:

* Uber being a simple CRUD app

* Uber having no moat because it can easily be disrupted by those able to build the app over a weekend, because Uber is a simple CRUD app

* Uber continuing to operate solely because VC's currently subsidize every single ride


* Medallion taxis being safer because they're more rigorously inspected.

Free space in the middle: drivers have no clue as to their costs and are only signing up because they're too naive to realize they're losing money.


Just because it's a spot on your bingo card does not mean it's not true.


My experience with all this is that each time I opened the Uber app, I actually did not know what I was going to find. Taking an Uber a few times a month meant finding new UX patterns every time, sometimes features would be there, sometimes they wouldn't. It was very annoying to find that useful features had suddenly disappeared. After feeling that the UX was hostile due to this, and some IRL hostile experiences with drivers with a terrible support follow-up, I just dropped Uber for good. Alternative apps were more expensive but absolutely more stable.


I am not an app developer, so ignorant. Why not have different apps? Uber-India, Uber-Mexico, etc? Or if it is to be all one Global Uber app, why not have expansions that are downloaded for other regions as needed?


Would you want to download a different app every time you traveled to a new country? If you're in the EU/APAC with many countries close together and travel for business?

Uber works relatively well in every country you go to, including at one point, China. Not many apps have ever accomplished that.


I wouldn’t mind if the app started up and said, “download the $country_or_region extension,” but that is just me (assuming it doesn’t require re-creating an account, or re-entering billing details).


Get off the plane in a developing country. Some of those airports are not nice. Some of them are nice shells that are almost deserted, with no wifi and a couple vendors that won't take your American credit card. Your "World Plan" often works, sorta, but you can't always get data due to different radio bands or because the network is simply not good enough for a 100+mb download. You'll either have to hop into a hopefully licensed taxi, or start walking in the dark in an industrial area (where airports usually are) in order to find a wifi hotspot or a street vendor that will sell you a SIM card. It's not fun.

Uber made the right choice.


Why does it take 100MB to download the business rules for that particular country? It will take some data, but you will need some to book the taxi anyway.


Pretty much the last thing I want to do every time I step off a plane is download, install, and sign into a new app before I can get into a car to go to my hotel.


Imagine yourself at an airport and trying to order Uber, but be forced to download new 50 MB app. You would immediately try some other app like Bolt or Lyft.

I'm from EU country and it was so convenient to be able to use Uber in NYC the same way as I would in my home town.


Friction and convenience. Always assume the user will not take the desired action because it most closely models reality.


Look, I get it that the app is complicated. I look at these apps from the perspective of a reverse engineer but invariably I find places where there is bloat just for the sake of being bloat. There is code that is provably not used, entire libraries that included just so that one small part of it can be used. I see time-related assets that have existed long past the date that they could have possibly been useful. I have seen apps that bundle entire scripting runtimes, apps that have half a dozen in-house telemetry libraries, apps that have megabytes of files in them in the hopes that they may be useful someday.

At this point it's really hard to believe the argument that "but our app is more complex that you could possibly understand" is valid. Compare your list with a Linux distribution from a handful of years ago: do you have more "screens" than that? Can you do more?


Eh. It doesn't seem particularly relevant to bring up Linux, with its hundreds of thousands of person-hours worth of volunteer contributions and an extremely technically focused BDFL gatekeeper.

It's a relatively well known fact that multi-team corporate projects have tech debt. There was a thread recently about Google Cloud being slow due to too-many-cooks-in-the-kitchen syndrome, and many ex-amazon folks say AWS UI is notoriously difficult to refactor for similar reasons.


I mean, Uber has had a huge number of people working on it too, with the incentive that they are paid to do a good job. Regardless of the eventual outcome, I have no misconceptions that Uber doesn't have some engineers who are extraordinary talented. And, the project is newer, too, with fewer guarantees on stability and fewer users to boot. I think it's a valid comparison to make at least–they are both huge, complex projects with a massive number of people working on them. Tech debt is inevitable, but I still think it is valid to call it out when it occurs and not try to excuse it with "but our app is super complicated, you can't possibly understand how complicated it is".


To be clear, I do think you have a valid point about e.g. unused assets and loading big libs to do trivial things, but IMHO, that's really a commentary on software development at large, rather than anything specific to Uber.

And there are cases, even in the Uber app, where thought has been put into optimizing both for upfront download size and internal complexity (the legal pages, for example)

Something that is worth considering is that a lot of what was said about the app being complicated is that it is also _dynamically_ complicated. For example, in the driver app, a feature was added to ensure drivers are wearing face coverings. The backend for that does AI-based fraud detection to prevent shenanigans like photos of photos. The picture upload flow is just one small part of a much more complex project that had to be rushed out because the covid pandemic happened. There are a bunch of similarly large impact things that touch several of the Uber apps simultaneously, often in non trivial ways and with tight timelines (e.g. features for compliance with specific legal requirements)

The reason I think Linux seems like a bad comparison is that if Linux contributors don't want to support some random driver for 3 years, that might be annoying but is generally just the way it is. Uber can't really afford to not do some of the complicated things it does in a timely manner.


What I don't really understand is why so much of the dynamic content ends up being native code on the client. For example, let's say you need to have some of legal compliance page. But there is always changes you have to make in a bunch of regions, and the flow is constantly changing, or whatever. But why can't this be a DSL you download and deserialize into views? Why does there have to be a hundred different native views for this that all must be part of the app? I'm hearing all these tail-end cases that one region hits and interacts with for seconds and I guess I don't really understand why these things are not handled server-side, making the app a dumb client. After all, isn't that how the website surely works?

I understand that Linux doesn't necessarily always have the product marketing deadlines or whatnot that Uber might have, but it's saddled with legacy code too. When things have security issues, or the API is just awful, at some point someone needs to fix it. Especially if your business is being decimated by a bug or you are lacking in APIs that meet your usecase. I don't think it's as poor as a comparison as you might thing it is.


I think a lot of it is driven by dysfunction and legacy at Apple. Their whole mindset is they want to review every version of the app manually and every bit of code that executes must be signed. They don't allow a lot of obvious software engineering approaches as a consequence, like partially downloading the app and streaming the rest on demand even though it's an obvious thing to do for mobile apps.

Note how in this story the Android and iOS teams started a big rewrite at the same time, but the story consists exclusively of fuckups on the Apple size. Apple impose an arbitrary cellular download limit. Apple designed a language that can't handle more than 9 dynamic libraries before it hits scaling issues. Apple prevent you from using interpreters or other techniques to change the app after it's installed, unless you use a WebView in which case it's OK for mysterious reasons. Apple don't help even when major brands are weeks away from wrecking their business by hitting these limits. Apple Apple Apple. Meanwhile the Android guys are sitting pretty with Java and now a migration to Kotlin, which is (a) a much better language than Swift and (b) is dogfooded at large scale on the IntelliJ project so you know it scales (had no scaling problems with Kotlin and I've been using it for the last five years).


I think the argument is more that even if you were to magically get rid of the cruft introduced by institutional bloat, the app will still necessarily be large given the nature of the business. Uber isn't some outlier here either; Lyft, Ola, Yandex, GoJek, Grab, etc etc all have comparable iOS app sizes.


It sounds stupid, but I'm going to say that they're all doing it wrong. One commenter in this thread brought up the Uber app for Android and it's a fraction of the size–clearly, market constraints can make it possible to do better?


The Grand Bazaar of Istanbul and the Tower of Babel.


It's a bit unfair to compare the mobile app with the Linux distro, given that mobile apps aren't supporting dynamic feature loading (or weren't at that time), so everything should be bundled. Imagine that any "screen" you need to access _must_ be bundled within the distro. So, compared to Linux distro – you should have your local mirror of all possible features any particular user would like to have. For example, Gentoo handbook says "Hosting a mirror requires a minimum of 550 GiB available disk space." 550 GiB just to run a Chromium? No – to have an opportunity to run it when you'll need it, because it's all or nothing. Either you get every program with all of its features, or get nothing at all.


Oh, no, I wasn't including "you can download packages" in this–I want it to be a fair comparison. Take a medium-weight Linux distribution and put it on a laptop and go on an island somewhere, if you want to tilt the comparison in the favor of Uber, which can of course still download assets from the internet and be backed by a server doing significant work for it. At that point I think it's still difficult to make the argument that Uber is somehow composed of more "screens" or "edge cases" that it needs to handle.


Interesting. I assume other multinational apps run into this problem as well. Is Apple considering support for apps with country-dependent payloads in the future? Otherwise seems like a lot of bloat across all major apps.


It seems like most of these things should be downloaded on demand or at least after the initial install and in background.

Same with dev experience. You shouldn't have to compile things you are not using to dev.

This would give pretty much infinite scaling.

I do think Uber requires tons of engineers, but I disagree that all of this has to be downloaded immediately or bundle size has to be this big.

All of what can be logic wise should be server side. For instance Airport logic seems like one.


I'm now wondering why Apple doesn't let you ship different binaries for different regions?


As someone who lives in the US, but (pre-covid at least) regularly travels to the UK (where I am from), I wish regional listing was not allowed _at all_. It's incredibly frustrating to have to carry two phones with different App Store accounts to be able to switch between the UK and US versions of things - Uber is one of the (vanishingly) few that gets this right and why they have consistently been my default choice for rides.


You don't need to carry two phones, you can sign out from App Store and then sign in with the other country account to install new apps. Updating works for both accounts even if the second country account is not currently logged in.

Pretty bad UX, it's clear it wasn't designed for this use case, you have to go into Settings, but it works if needed.


Maybe iOS could keep on hand the different versions you have, and automatically switch based on your current location? Generalize the "lazy loading" so app developers don't have to deal with the problem themselves


It sounds like app developers would still have to deal with the problem, and pray that the OS gets the location logic right


They do (well, you can have different listings limited to different regions).

But basically the only time I use Uber is when I travel to another region. I don't want 30 different Uber apps on my phone...


Was just about to link to your tweet on this subject from the other day. You saved me the search!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: