How to solve the Facebook iOS App Design Interview — Amazon Engineer perspective

Last week, a reader contacted me to explain issues he was facing in his recent interview with Facebook. During the System Design challenge, he was asked to design a Mobile Application. Unlike the traditional System Design interview, this one focuses specifically on the architecture of the mobile application, and then moves into its many different details.

Since that reader has been part of a mobile application team, and his view point was rather limited, he did not have enough exposure to fully plan an application, and to help him prepare for such an interview. That’s why we’re going to explore exactly that in this article.

Clarification

When sitting in an interview and being asked to “design Instagram”, it’s the first and most crucial step to ask clarifying questions. What part of the app do we need to concentrate on? We can’t possibly design the whole application within 20 minutes — typically the amount of time dedicated for the actual design part. The remainder is reserved for questions from the interviewers, as well as yours.

Typically, interviewers want you to concentrate on a challenging and complex part of the application, like the “news feed”, or the “instant chat”.

A few examples of clarifying questions are:

  • What part of the app should I design? Authentication? Feed? User profiles? Messaging? Post comments? Application nowadays serve a multitude of purposes with an infinite amount of features.
  • (If for example a messenger app) What kind of attachments will the design support? If we don’t have to worry about images, then we don’t have to worry so much about caching. If we have to support map locations, do we need to worry about map performance? We need to ask the interviewer all kinds of questions to demonstrate our understanding of scalability.
  • Do I need to design uploading functionalities as well, or just the data output? For example, for a new feed, it’s possible that creating feed items is also part of the design. Although, it typically isn’t.
  • (If you’re as familiar with it as I am) Is it okay to assume a Firebase backend so I can demonstrate my strengths best with my past experience? Just ask the interviewer if it’s okay if you demonstrate an example utilizing an industry standard method you’re best familiar with.
  • (If some kind of social media feed) What kind of feed items? Facebook, for example, supports tons of different types of elements. Everyone of them might deserve its own microsystem and so we need to define the focus.

For this article, we’re going to create an Application Design for the “Instagram Home Feed”–so images and their meta-data. In an interview scenario, we’re now expected to lead the open conversation. We’re expected to explain what we will need to concentrate on, such as what aspects of modern application design influences our decisions.

Whether we interviewing for Amazon, Facebook, JP Morgan, Snapchat, TikTok or for startups, the basic and most common requirements overlap for all applications.

  • Scalability. We need to write an application that will be able to serve countless users, and handle an excessive amount of content all over the world.
  • Security. The application needs to at least match the high industry security standard in terms of authentication, networking and data storage.
  • High Velocity Development. Arguably as part of scalability, we need to support many users, as well as, many engineers. We need to lay out the foundation for a scalable code architecture that can support thousands of commits a day, without major disruption through merge conflicts.
  • Separation of Concerns. The design should demonstrate your seniority and understanding of code architecture in terms of clean code. Write services and managers that are easily interchangeable and are concerned about only themselves.

Overview

Once we know exactly what kind of functionality we have to develop, we will need to start the sketching process. We will start the design from the very lowest to the most top layer of the application. Starting from the AppDelegate, to the UIViewController.

Initial sketch of the elements we need so far

During an interview, you are required to sketch elements like above on a white board or some digital version thereof. I’ve created the sketch with Whimsical. Don’t worry about making it beautiful. The purpose is to support your verbal arguments with a visual aid.

Using the sketch, we want to demonstrate how we separate different layers, as well as concerns. We’ve created dedicated ...Manager classes and MicroServices for specific requirements and functionalities, so we can exchange them without breaking anything.

In an interview scenario, you are sketching one component, and then you’re explaining the ideas of it on the fly. The following list explains the purpose of each element, like I’d explain it on the fly as well.

  • NetworkingManager - Responsible for all requests that go outside of the application. Firebase, as part of an SDK, with its own wrapper of network operations would be left out of the following, but with a networking manager, the idea is to wrap all operations that interact with the internet. So all kinds of Alamofire or URLSession operations and tasks. Another example could be a delegate method to the AppDelegate that could cause some kind of UI banner, notifying the user of missing network connection, and kicking off some kind of offline mode.
  • DatabaseManager - Responsible for taking in database requests and returning states to potentially notify the user of issues. The advantage of wrapping database operations inside a wrapper class, is best demonstrated here. We could replace Firebase with AWS, without the rest of the application being affected at all. Only one class needs to be adjusted.
  • UserManager - Responsible for all auth related operations. Logging In. Logging Out. Provides some kind of observable API to make sure the app switches state when the auth state changes. A class best implemented in a LandingController class. When the auth state switches from user to nil, LoginViewController gets presented, and once logged in, FeedViewController.
  • AppDelegate - Instances for most managers are created, and passed down the view hierarchy using dependency injection. A NetworkingManagerProtocol could then have its delegate in the AppDelegate, and state changes could be kept and processed from there, creating visual indicators for the user.
  • FeedManager - Responsible for getting feed items. Has dependency on the DatabaseManager, and wraps everything needed from database to feed list. Casting database items to FeedItem objects, as well as caching, and handling errors and states. Providing APIs for its consumers. For example the FeedListViewController.
  • ImageManager - Provides a one-stop API to get images based on a URL parameter. It wraps the ImageLoader, and provides APIs like getImage(forURL) and uploadImage(forElement,image) and handles everything from loading, caching, all the way to error handling.
  • ImageLoader - Not exposed to consumers of the ImageManager, but a private dependency or initiation within, the loader itself has a dependency on the NetworkingManager. Remember: All networking requests are meant to go through one dedicated API.
  • ImageCache - Dependency of the ImageLoader. Provides an API to cache and retrieve images into and from the memory cache. Controlled using an LRU. A least recently used object queue.
  • UIViewController - It's important to mention to the interviewer that the view controller's only concern is to present elements to the view, and define view specific attributes. The remainder of the business logic is handled by prior defined API wrappers, the ...Managers classes.

Next, during the interview, we’re typically asked to design a few example APIs, for parts of the sketched design, based on the interviewer’s choice. Many interviewers like to talk about image loading and caching.

enum Image { // 1
case image(UIImage)
case thumbnail(UIImage)
}
enum LoaderResult { // 2
case success(Image)
case failure(Error)
}
class ImageLoader { // 3
func getImage(from url: String, onLoad: @escaping ((LoaderResult) -> Void))
}
  1. Image enum, to demonstrate that we will have thumbnails for reduced perceived latency of the user. Demonstrates that performance is important to us.
  2. Result enum, to demonstrate that we write clean code and define secure types, and understand how to structure code well. Those are all important aspects of scalability.
  3. Dedicated ImageLoader, that demonstrates "separation of concerns". Having a clear, one-stop service to handle all "image loading" requirements. A clear defined API for future consumers.

With above in place, we have demonstrated that we understand how to generally structure a mobile application and are no strangers to the separation of concerns. We’ve also shown that we understand that writing dedicated APIs to avoid having to change countless consumers when underlying technologies change, increases “sprint predictability”. This is an important part of the agile development cycle.

As an example, if we need to fix a bug related to image caching, we know exactly where to look. Only one part of the whole application handles image caching related activities, and once the bug in our ImageCache is fixed, the whole application's image caching is fixed. We will not have to perform some kind of static analysis to find all lines of code, that potentially individually handle caching.

Scalability

Scalability comes in all kinds of equally important facets. Before we even reach the phone, we need to make sure our system is scalable. Vertically, by scaling up, as well as horizontally, by scaling out. We want to be able to support millions of users within the United States, as well as across the world.

During the System Design interview, we need to be aware of concept like regional server farms and networks, with load balancers and caching layers. When designing the Application Architecture, we need to think about similar concepts and ideas.

We need to cache data locally, so hundreds of millions of requests are not unnecessarily overloading the backend system. We also need to define an application architecture that supports adding new functionality and fixing bugs by countless engineers, while supporting an extraordinary “speed to customer”.

Examples include, the DatabaseManager must utilize some kind of a pagination or cursor controlled approach. A whole user's feed does not need to be downloaded all at once. We'd download 30 items at a time, while pre-rendering 20 for a low latency, and continuing with the "next page" when scrolling.

Another common measure to support a high scale of users and requests, is throttling. A 3-second limit on the “pull-to-refresh” action, could decrease unnecessary load on the backend. It always depends on the specific environment of the given application, and how often a manual user refresh is desired, needed or required.

Firebase uses an observable. The view refreshes without manual request. But the Firebase instance could be some kind of pre-computed, REDIS kind of database. The refresh pull could kick off a new pre-computation, to include more recent posts.

A last measure worth mentioning, within the context of a short article, is caching. For a news feed, we wouldn’t want to run the expensive execution of downloading an image multiple times just because the users scrolls the feed up and down. The image is downloaded once, and then stored in a memory cache. That decreases network operations, and perceived latency for the user.

All of the above are thoughts you need to describe out loud to the interviewer, and you need to understand the underlying concepts to a point where you are able to debate the pros and cons.

Security

Security is a huge topic, which is why I’m dedicating a whole, upcoming article just to the topic of “securely storing API keys”. However, for now, I’m going to provide a few “buzzwords” you should use to prepare for the interview, as well as for future research.

During the interview, it’s perfectly acceptable to say that we are not security experts. There’s a whole profession specialized and highly educated around the whole topic of network and data security. Regardless, we are still, and rightfully so, expected to have a basic understanding of potential risks, as well as industry best practices.

In my personal example, I would simply be open and disclaim that most of my experience has been with Firebase, where authenticating users, storing tokens, and performing further security measures has mostly been outsourced to the Firebase SDK, and therefore I’ve had little exposure to custom implementations.

Most interviewers will then move to a different topic, to provide a better opportunity to shine. Always remember: the interviewer wants you to succeed.

Of course, when interviewing for smaller companies and startups, they might need for us to be very proficient in security measures. If we’re unable to satisfy that requirement, then it’s in the mutual best interest to part ways. We can pursue other opportunities, and that company will find someone better qualified for their needs.

In most big companies, on the other hand, specialists are concerned with security, and those companies are primarily looking for generalists, or of course, for some specialists in areas like ReactNative or AR.

To name a few examples of best practice concepts:

  • We won’t store sensitive information in the UserDefaults, as that storage does not enjoy higher encryption, and is therefore not safe against exploitation.
  • One trick to safely secure API keys is to utilize a private CloudKit database. This is potentially vulnerable against “man in the middle” attacks. But every idea in security can be countered with a “but”.
  • A good practice to securely store Secrets, is to utilize a .xcconfig configuration file, ignore the file using a .gitignore, and reference the Secrets within the configuration file in code. That way, you're not exposing Secrets in your repository.

Caching

There are many different caching mechanisms and ideas. For example, it’s common to cache images in memory. A messaging application is more efficient when retrieving images from memory rather than downloading it every time.

In our interview example, the FeedManager downloads feed items utilizing the DatabaseManager. That manager will have an internal cache implementation using, for example, CoreData. But the underlying technology is not exposed. That's the "separation of concerns".

As an additional example, to display a user profile, we need to download information to display it. Once downloaded, the cache will now have this information. We can display the profile instantly when requested. And while presenting the new controller, a request updates the information. Update the UI, and update the cache.

An inevitable follow-up question is typically how to handle how much data is stored in the cache. An Instagram feed contains hundreds, if not thousands, of feed items. Storing all of them would blow up even a hard disk storage, very fast.

To handle this kind of threshold, we’d typically implement an LRU, a Least Recently Used object queue. The FeedManager would download new feed items and the LRU stores the new items and kicks out the least recently used item.

The predicate for such an operation could, of course, be customized. For example, kicking out items that are supposed to be at the most bottom of a potential feed, as opposed to the most top items.

Offline Support

Naturally, given what users nowadays are able to expect from any kind of successful application, we’ll most likely be asked to have some kind of idea how to support an offline mode.

In the Instagram example, we would need to make sure that user authentication and the stored user model supports offline availability. Furthermore, we should be displaying the feed items the user saw last.

Based on the caching mechanisms discussed before, we are already very far along in planning offline support.

In the past I’ve implemented a custom Offline Database of a Firestore as following.

class DatabaseManager { // 1
private let offlineDatabase = OfflineDatabase() // 2
private let onlineDatabase = OnlineDatabase() // 3

public func query(_ path: QueryPath, onLoad: @escaping (DatabaseResult) -> Void) { // 4
offlineDatabase.observe(path) { result in // 5
onLoad(result)
}

onlineDatabase.query(path) { result in // 6
offlineDatabase.store(result, forQueryAt: path)
}
}

public func store(_ payload: Payload, forQueryAt path: QueryPath) {
offlineDatabase.store(payload, forQueryAt: path { scheduler in
// 7
}
}
}
private class OfflineDatabase {
public func observe(_ path: QueryPath, onComplete: @escaping (DatabaseResult) -> Void)
public func store(_ payload: Payload, forQueryAt path: QueryPath, onStore: ((SchedulerResults) -> Void)? = nil)
}
  1. As always, we want to demonstrate that we’ll write clean and well-structured code, with separation of concerns. We will have a dedicated model for all database related tasks.
  2. We’ll have a dedicated OfflineDatabase
  3. And a dedicated OnlineDatabase
  4. A query function that takes a QueryPath, some type of structure we specify as a prerequisite. We would communicate to the interviewer that we'd plan on having such types, but we will not be expected to implement every one of them.
  5. The idea is to observe the offline database and forward the information to the closure, down to the consumers.
  6. Then, we’re using the online database, to update the offline database. And since in #5 above, we said that we’re observing on the offline database, new information will automatically be forwarded to the consumers.
  7. Any kind of uploading functionality has not been set as requirement for this interview, but we provide a stub to demonstrate that the upload functionality will also be handled in this wrapper.

I hope that the concepts and ideas explained above will give you more of an understanding of how I would tackle the application design interview. I’m not saying that my way is the only way, or even the best way, but I’m offering my thoughts to help you get your own ideas.

It’s important to understand that the points mentioned above are explained and discussed on a surface level. There is so much more to say. It’s unlikely, however, that any interviewer will want you to go much deeper into details of any of them; and I don’t want to have articles that take two hours to read.

The goal of the exercise–the interview challenge–is to find out if you have a clear and accurate overall understanding of how to write a solid, secure, and scalable application that follows coding best practices.

You can find this story, and more at my personal blog,

Software Engineer at Amazon (Alexa Mobile)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store