Implementing Payments in the DoorDash Android App

Zach Lloyd, the interim CTO of Time Magazine, talks about the biggest mistake engineers make. Plus, DeepMind builds a model that can solve leetcode questions, how Rust fixes C++'s issues and more!

Hey Everyone,

Today we’ll be talking about

  • The Biggest Mistake Engineers Make

    • Zach Lloyd was previously a Principal Engineer at Google where he ran the Google Sheets team. After, he was the interim CTO of Time Magazine and is now the CEO of a startup

    • He’s publishing an awesome handbook documenting his management style as an Engineering Manager / CTO

    • The biggest mistake he sees engineers make is not doing their work iteratively with the product team

  • Lessons Learned from Implementing Payments in the DoorDash Android App

    • Hash Alkutkar is a software engineer on DoorDash’s ordering experience team. He wrote a great blog post on eight things we learned from implementing payments in the DoorDash Android App

    • Plan and design for adding future payment methods

    • Add lots of telemetry to make it easier to debug your payment flows.

  • Plus, some awesome tech snippets on

    • How Rust fixes C++’s issues (from a C++ programmer)

    • Etsy migrated all of their React code to use Preact

    • DeepMind builds a model that can solve LeetCode questions

    • Databricks writes about their experiences with Scala

Plus, an answer to our last interview question and a new question from Microsoft.

Questions? Please contact me at [email protected].

Quastor is a free Software Engineering newsletter that sends out summaries of technical blog posts, deep dives on interesting tech and FAANG interview questions and solutions.

The Biggest Mistake Engineers Make

Zach Lloyd was previously a Principal Engineer at Google where he ran the Google Sheets team. After, he was an interim CTO at Time Magazine and is now the CEO of a startup building a Rust-based terminal.

He’s also publishing a handbook documenting his management style as a CTO / engineering manager.

This is a summary of one of his posts on the biggest mistake he sees engineers make.

Summary

The biggest mistake Zach sees engineers making is doing too much work on their own before looping in others.

A typical scenario goes down like this

  1. An engineer takes on a big project. The bigger the project, the likelier it is that the situation will follow this pattern.

  2. The engineer goes off on their own and starts by trying to figure out how to build the thing. This is usually called “exploratory work” and doesn’t have a concrete deliverable.

  3. After several weeks, the engineer shares an update and one (or many) of the following bad things happen:

  • They have been solving the wrong problem because the feature wasn’t well specified.

  • They have been trying to figure out the solution for a very specific part of the problem, but that problem wasn’t important at all and the product requirements could easily be trimmed back to avoid it.

  • They designed a very involved and complex solution which could’ve been simplified greatly if they had gotten feedback earlier.

  • The engineer started building the thing and is about to send out a really big initial PR for it. This is bad because you should send the smallest PRs you can, and for big features you should start with a design/prototype, not a PR.

This obviously causes a huge waste of time and it can also cause the team to work on the wrong thing.

Sunk cost fallacy makes it hurt to abandon the work already done (even if the work is on the wrong path) and the team can end up shipping the wrong thing because they didn’t want to swallow the sunk cost.

Instead, the work of an engineer should be developed iteratively with the product team.

Engineering Managers should

  • Always encourage engineers to show their work as quickly as possible. An engineer on a project should never go more than a week without showing something and getting feedback.

  • Engineers should get something end to end launched internally as quickly as possible. This will give everyone a better sense of the feature from the user’s perspective and will also be the easiest way to see how the pieces of code fit together.

  • Encourage everyone on the team to give constructive feedback. Be on the lookout for any feedback that is overly negative, since that discourages developers from demoing their work and working iteratively.

You can read Zach’s full blog post here.

Quastor is a free Software Engineering newsletter that sends out summaries of technical blog posts, deep dives on interesting tech and FAANG interview questions and solutions.

Tech Snippets

  • How Rust fixes C++’s issues (from a C++ programmer) - Jimmy Hartzell is a senior C++ developer/instructor. Previously he worked at Tower Research Capital (a hedge fund that specializes in high frequency trading) as a C++ dev. This blog post focuses mainly on the syntactical differences between Rust and C++ and how Rust improves some of C++’s shortcomings.In Part 2, Jimmy talks about move semantics in C++ vs. Rust and why Rust is an improvement.In Part 3, Jimmy compares memory safety between the two languages.

  • Etsy finished migrating all of their React v15.6 code to use Preact (a React alternative with the same API but with performance benefits).You can read Etsy’s rationale for choosing Preact here.

  • Solving Competitive Coding questions with Deep Learning - DeepMind has unveiled AlphaCode, which uses transformer-based language models to generate code. They validated performance using competitions on Codeforces, where Alphacode placed at the level of the median competitor. This is the first time an AI code generation system has reached a competitive level of performance in programming competitions.

  • Scala at Scale at Databricks - Databricks is a software company that makes data engineering easier. It was founded by the creators of Apache Spark. Databricks is one of the largest Scala shops around, with millions of lines of Scala being used in production. This post goes into why they picked Scala, the tooling they use, issues they’ve come across and more.

Lessons Learned from Implementing Payments in the DoorDash Android App

Harsh Alkutkar is a software engineer on DoorDash’s ordering experience team. He wrote a great blog post on Eight Things We Learned from Implementing Payments in the DoorDash Android App

Summary

How mobile payments are typically implemented

When a user is making an online order, they will submit their credit card information to a payment gateway such as Stripe or PayPal. The gateway encrypts this information and facilitates the transaction with payment processors.

The payment processor will talk to the issuing bank (for the user’s credit card) and request approval.

The approval will then bubble to the backend, which lets the client know if the payment was accepted/declined.

Mobile payments can become very complex for the following reasons

  • Multiple Payment Methods - In order to boost conversion rates, you want to offer as many payment methods as possible to the user. Each method requires its own integration into the app and requires its own custom testing strategy.

  • User experience - The UI needs to work with all payment methods and also for new and existing users. This creates quite a few scenarios that have to be implemented and tested.

  • Testing - Testing cannot be an afterthought. The backend has to be designed in a way that allows every payment method and flow to be tested before a release.

  • Fraud - Anti-fraud measures need to be implemented in any app that includes a mobile payment component.

  • Location - you need to account for the user’s location before processing their payment to comply with each country’s laws and regulations

Here’s a couple of the lessons DoorDash engineers learned while implementing payments in the DoorDash Android app.

Plan and design for future payment methods

In an earlier version of the DoorDash app, the developers didn’t properly account for the addition of new payment methods to the app. Instead, it was designed around credit cards and Google Pay. This led to challenges when adding new methods like PayPal.

In the new design, engineers introduced the notion of payment methods into the codebase. These are categorized into local payment methods that are part of the device (like Google Pay) and external payment methods that require interactions with a payment gateway (like Stripe).

Beware of restrictions and implementation guidelines specific to payment methods

Payment vendors may have specific ways they want to be portrayed in an app.

For example, Google Pay requires that it be the primary payment option wherever possible.

Additionally, they have strict UX guidelines that explain how to display their logos and buttons.

Plan for consumers in different countries or traveling consumers

Payments usually can’t be implemented in a generic way that scales worldwide. Each country has its own technical, legal and accounting implications.

Some payment methods may also need extra verification or information in other countries.

Plan for performance

It’s absolutely critical to keep an app performant while it processes payments. Caching the payment methods and cards makes it faster because there’s less waiting for payment information on the cart and checkout screens.

However, this has to be done with care and must account for error cases where the backend and device are out of sync.

Add lots of telemetry

Payment flows can be tricky to debug if they don’t work properly. Therefore, instead of just sending generic information like “payment failed”, the system should send as much information as possible; including things like error codes from providers, device information or any diagnostics that can help to identify the state of the app when it failed.

However, be careful not to include any personal identifiable information or any payment information that could be compromised by an attacker.

Quastor is a free Software Engineering newsletter that sends out summaries of technical blog posts, deep dives on interesting tech and FAANG interview questions and solutions.

Interview Question

You are given an array that contains an expression in Reverse Polish Notation.

Return the result from evaluating the expression. You can assume the expression will always be valid.

Input - [“2”, “3”, “+”, “3”, “*”]

Output - 15 because ( (2 + 3) * 3)

Input - [“4“, “5“, “/“, “30“, “+“]

Output - 30.8 because ( (4 / 5) + 30)

Here’s the question in LeetCode.

Previous Solution

As a reminder, here’s our last question

You are given a linked list with n nodes.

The nodes in the linked list have a next and prev pointer, but they also have a random pointer.

The random pointer points to a randomly selected node in the linked list (or it could point to null).

Construct and return a deep copy of the given linked list.

Solution

One way of solving this question would be with a hash table.

You create a hash table mapping the nodes from the old linked list to the nodes of the deep copy linked list. The keys in the hash table would be the nodes of the old linked list and the values would be the respective nodes in the deep copy.

You iterate through the given linked list and for each node in the linked list you

  1. Check if the current node has a mapping in the deep copy. If not, then create a node in the deep copy that is a copy of the current node. Create the mapping in the hash table.

  2. Check if the next node has a mapping in the deep copy. If not, then create a node in the deep copy that is a copy of the next node. Create the mapping in the hash table.

  3. Check if the random node has a mapping in the deep copy. If not, then create a node in the deep copy that is a copy of the random node. And… create the mapping in the hash table.

After iterating through the entire linked list, you can check the hash table for the copy of the original head node and return that.

Here’s the Python 3 code…

The time and space complexity are both linear.

However, this isn’t the most optimal way of solving the question. We can actually do it without using any additional data structure (other than new deep copy linked list we’re constructing).

The way we do it is by augmenting the original linked list.

We’ll first iterate through the original linked list and create a new node in between the cur node and the cur.next node.

This new node has the same value as cur and is meant to be cur’s copy in the deep copy linked list. We’ll ignore the random pointer for now.

We go through the entire linked list and create the new nodes.

So, if we had a linked list that looked like

1 -> 2 -> 3 -> 4

It will now look like

1 -> 1 -> 2 -> 2 -> 3 -> 3 -> 4 -> 4

Now, we iterate through the augmented linked list and set the random pointers for the new nodes we created.

Since the deep copy node will be immediately after the original node, we know that the deep copy node for the node pointed at by our random pointer will also be immediately after that node.

After setting all the random pointers, we’ll have to iterate through the augmented linked list again.

Now, we’ll be breaking the linked list up into two linked lists. One linked list will be all the original linked list nodes and the other linked list will be the deep copy nodes.

Now, we can return the deep copy linked list.

Here’s the Python 3 code.