[qns] sd: remove H5
This commit is contained in:
parent
8259e79fe3
commit
a4ede698bc
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -134,15 +134,13 @@ If we detect that the device has entirely lost network connection, there's not a
|
|||
|
||||
What is the cache for? Caches are typically used to improve performance of queries and reducing processing costs by saving the results of previous queries in memory. If/when the user searches for the same term again, instead of hitting the server for the results, we can retrieve the results from memory and instantly show the results, effectively removing the need for any network request and latency.
|
||||
|
||||
Google and Facebook search boxes cache their queries!
|
||||
To provide the best experience, Google and Facebook search inputs cache user queries.
|
||||
|
||||
#### Cache structure
|
||||
|
||||
The cache within the component is the most interesting aspect of the component as there are many ways to design the cache, each with its own pros and cons. Explaining the tradeoffs of each is essential to acing front end system design interviews.
|
||||
|
||||
##### **1. Hash map with search query as key and results as value**
|
||||
|
||||
This is the most obvious structure for a cache, mapping the string query to the results. Retrieving the results is super simple and can be obtained in O(1) time just by looking up whether the cache contains the search term as a key.
|
||||
**1. Hash map with search query as key and results as value.** This is the most obvious structure for a cache, mapping the string query to the results. Retrieving the results is super simple and can be obtained in O(1) time just by looking up whether the cache contains the search term as a key.
|
||||
|
||||
```js
|
||||
const cache = {
|
||||
|
|
@ -176,9 +174,7 @@ const cache = {
|
|||
|
||||
However, see that there are lots of duplicate results especially if we don't do any debouncing as the user is typing and we fire one request per keystroke. This results in the page consuming lots of memory for the cache.
|
||||
|
||||
##### **2. List of results**
|
||||
|
||||
Alternatively, we could save the results as a flat list and do our own filtering on the front end. There will not be much (if any) duplication of results.
|
||||
**2. List of results.** Alternatively, we could save the results as a flat list and do our own filtering on the front end. There will not be much (if any) duplication of results.
|
||||
|
||||
```js
|
||||
const results = [
|
||||
|
|
@ -199,9 +195,7 @@ const results = [
|
|||
|
||||
However, this is not ideal in practice because we have to do filtering on the client side. This is bad for performance and might end up blocking the UI thread and is especially evident on large data sets and slow devices. The ranking order per result might also be lost, which is not ideal.
|
||||
|
||||
##### **3. Normalized map of results**
|
||||
|
||||
We take inspiration from [normalizr](https://github.com/paularmstrong/normalizr/tree/master/docs) and structure the cache like a database, combining the best traits of the earlier approaches - fast lookup and non-duplicated data. Each result entry is one row in the "database" and is identified by a unique ID. The cache simply refers to each item's ID.
|
||||
**3. Normalized map of results.** We take inspiration from [normalizr](https://github.com/paularmstrong/normalizr/tree/master/docs) and structure the cache like a database, combining the best traits of the earlier approaches - fast lookup and non-duplicated data. Each result entry is one row in the "database" and is identified by a unique ID. The cache simply refers to each item's ID.
|
||||
|
||||
```js
|
||||
// Store results by ID to easily retrieve the data for a specific ID.
|
||||
|
|
@ -242,18 +236,18 @@ const cache = {
|
|||
|
||||
There will be pre-processing that needs to be done before showing the results to the user, to map a list's result IDs to the actual result items but the processing cost is negligible if there are only a few items to be shown.
|
||||
|
||||
##### **Which structure to use?**
|
||||
**Which structure to use?**
|
||||
|
||||
Which to use depends on the type of application this component is being used in.
|
||||
|
||||
- **Short-lived websites**: If the component is being used on a page which is short-lived (e.g. Google search), option 1 would be the best. Even though there is duplicated data, the user is unlikely to use search so often that memory usage becomes an issue. The cache is being cleared/reset when the user clicks on a search result anyway.
|
||||
- **Long-lived websites**: If this autocomplete component is used on page which is a long-lived single page application (e.g. Facebook website), then option 3 might be viable. However, do also note that caching results for too long might be a bad idea as the results will be stale and hog memory.
|
||||
- **Long-lived websites**: If this autocomplete component is used on page which is a long-lived single page application (e.g. Facebook website), then option 3 might be viable. However, do also note that caching results for too long might be a bad idea as stale results take up memory without being useful.
|
||||
|
||||
#### Initial results
|
||||
|
||||
Noticed how on Google search, when you first focus on the input, there is already a list of results even though you haven't typed anything yet? Showing an initial relevant list of results could be useful in saving users from typing and reducing server costs.
|
||||
Noticed how on Google search, when you first focus on the input, a list of results is displayed even though you haven't typed anything yet? Showing an initial relevant list of results could be useful in saving users from typing and reducing server costs.
|
||||
|
||||
- **Google**: Popular search queries today (current affairs, trending celebrities, latest happenings) or historical searches
|
||||
- **Google**: Popular search queries today (current affairs, trending celebrities, latest happenings) and historical searches
|
||||
- **Facebook**: Historical searches.
|
||||
- **Stock/crypto/currency exchanges**: Historical searches or trending stocks
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ Chat applications have the following characteristics:
|
|||
- Messages do not have to (and should not!) be indexed by search engines.
|
||||
- Fast initial loading speed is desired but not the most crucial.
|
||||
|
||||
Considering the above, a pure client-side rendering and single-page application overall architecture will work well. We can use server-side rendering (SSR) with client-side hydration like for [News Feed](/questions/system-design/news-feed-facebook) and [Photo Sharing applications](/questions/system-design/photo-sharing-instagram) for a fast initial load but the benefits of SSR will only be limited to performance gains because chat applications don't need to be SEO-friendly. The additional engineering complexity of enabling SSR might not be worth it.
|
||||
Considering the above, a pure client-side rendering and single-page application overall architecture will work well. We can use server-side rendering (SSR) with client-side hydration like in [News Feed system design](/questions/system-design/news-feed-facebook) and [Photo Sharing application system design](/questions/system-design/photo-sharing-instagram) for a fast initial load but the benefits of SSR will only be limited to performance gains because chat applications don't need to be SEO-friendly. The additional engineering complexity of enabling SSR might not be worth it.
|
||||
|
||||
### Architecture diagram
|
||||
|
||||
|
|
|
|||
|
|
@ -285,18 +285,16 @@ OT was originally developed for collaborative editing of text documents, allowin
|
|||
|
||||
OT systems typically use a replicated document storage model, where each client maintains a local copy of the document. Users operate on their local copies, and changes are propagated to other clients. When a client receives changes from another client, it applies transformation functions to ensure that the local document remains consistent with the updates.
|
||||
|
||||
##### **Key components of OT**
|
||||
The key components of OT include:
|
||||
|
||||
1. **Operations**: These are the basic actions users perform, such as inserting, deleting, or modifying text. Each operation is associated with a position in the document.
|
||||
2. **Transformation functions**: These functions determine how to adjust operations when they conflict. For example, if two users insert text at the same position, the transformation function will resolve the conflict to maintain a consistent document state.
|
||||
3. **Control algorithms**: These algorithms manage the sequence and context of transformations. They decide which operations should be transformed against others based on their causal relationships, ensuring that the order of operations respects the intent of each user.
|
||||
|
||||
##### **Example of OT**
|
||||
Let's revisit the Alice and Bob example and see how applying OT can solve the issue.
|
||||
|
||||

|
||||
|
||||
Let's revisit the Alice and Bob example and see how applying OT can solve the issue.
|
||||
|
||||
1. **Alice before Bob**: The server receives Alice's request before Bob's, resulting in the server deleting the fourth character then the second character "ABCDE" -> "ABCE" -> "ACE". In this scenario, the document on the server correctly deletes Alice's and Bob's intended characters. However, Bob's computer realizes that Alice's change was made before Bob's deletion, hence it transformed Alice's change from `DEL @3` -> `DEL @2` since Bob deleted an earlier character. Bob ends up with "ACE".
|
||||
2. **Bob before Alice**: The server receives Bob's request before Alice's, resulting in the server deleting the second character: "ABCDE" -> "ACDE". When the server receives Alice's request, it realizes that Alice's change was made before Bob's deletion, hence it transforms Alice's change from `DEL @3` -> `DEL @2` to account for Bob's deletion of an earlier character. The server correctly deletes "D" (the third character) and passes this transformed operation to Bob. Both the server and Bob end up with "ACE".
|
||||
|
||||
|
|
@ -309,20 +307,14 @@ How does the server know whether Alice's and Bob's requests were made with or wi
|
|||
|
||||
Requests and responses can include the document revision number so that both servers and clients know the document version the other party was seeing when the request was made and can correctly determine if operations require transformation.
|
||||
|
||||
##### **Analysis of OT**
|
||||
|
||||
Let's take a look at how OT fulfills the conflict resolution properties:
|
||||
**Analysis of OT**: Let's take a look at how OT fulfills the conflict resolution properties:
|
||||
|
||||
- **Convergence**: OT uses transformation functions to modify operations so that they can be applied consistently across all replicas, even if they arrive in different orders. The core idea is that if two operations conflict, the transformation functions adjust one or both operations to ensure they can be applied in any order but still lead to the same final state.
|
||||
- **Causality preservation**: OT systems often use causal history tracking to ensure that operations are applied in an order that respects their causal dependencies. This is usually done by tagging operations with metadata, such as timestamps or revision numbers, which indicate their causal relationships.
|
||||
- **Intention preservation**: OT payloads commonly include the command intention and context (document revision number). The transformation functions consider the context in which an operation was originally applied. The command along with the contextual awareness helps in preserving the original intention even when the document has changed due to other concurrent operations.
|
||||
|
||||
##### **Drawbacks of OT**
|
||||
|
||||
OT works well for text-based documents but can be less effective for other types of data structures, such as hierarchical or non-linear data. Implementing OT for these types of data requires additional effort and customizations.
|
||||
|
||||
##### **Further reading**
|
||||
|
||||
There are many implementations of OT algorithms and various consistency models, which are beyond the scope of this article. If you're interested, check out the following links:
|
||||
|
||||
- [What’s different about the new Google Docs: Conflict resolution](https://drive.googleblog.com/2010/09/whats-different-about-new-google-docs_22.html)
|
||||
|
|
@ -335,7 +327,7 @@ If you were wondering – why use offset indices for the updates when they are h
|
|||
|
||||
CRDTs are advanced data structures designed for distributed systems, enabling multiple users or applications to update shared data concurrently without coordination which eventually converge to the same state (strong eventual consistency) when all updates have been received and applied. Instead of resolving conflicts using OT, CRDTs are built so that the operations performed on the data are inherently conflict-free or can be automatically resolved in a consistent manner.
|
||||
|
||||
##### **Properties of CRDTs**
|
||||
CRDTs have the following properties:
|
||||
|
||||
1. **Concurrent updates**: CRDTs enable independent updates across multiple replicas of data. Each replica can be modified without needing to coordinate with others, making them ideal for environments where network connectivity may be intermittent. Updates can be propagated using the gossip protocol without the need for a central authority.
|
||||
2. **Monotonic increasing updates**: Updates to a CRDT must be monotonic, ensuring that new values are always greater than or distinct from previous values. This allows for a clear progression of state changes.
|
||||
|
|
@ -345,34 +337,26 @@ CRDTs are advanced data structures designed for distributed systems, enabling mu
|
|||
|
||||
The CRDT model is somewhat similar to Git – every developer in the organization possesses a copy of the repository and is allowed to make changes locally. At the end of the day, the developers can merge changes with every other developer however they like: pair-wise, round-robin, or through a central repository. Once all the merges are complete, every developer will have the same state. However unlike Git, a CRDT prescribes a way to merge conflicts automatically and can merge out-of-order changes.
|
||||
|
||||
##### **Example of CRDT – Grow-only set**
|
||||
|
||||
An example of a CRDT is a grow-only set. A grow-only set is an unordered set that only allows addition of unique elements. It is a CRDT because:
|
||||
**Example of CRDT – Grow-only set**: An example of a CRDT is a grow-only set. A grow-only set is an unordered set that only allows addition of unique elements. It is a CRDT because:
|
||||
|
||||
- The set can be replicated.
|
||||
- Each replica can add any element it likes and the addition is a monotonically increasing update.
|
||||
- Each replica can be merged back together in any order.
|
||||
- Once all merges are complete, all replicas will have the same contents (the union of all individual sets).
|
||||
|
||||
##### **Representing text in CRDTs**
|
||||
|
||||
Collaborative text documents can be represented using sequence CRDTs like lists (e.g. [Linear Sequences and Replicated Growable Array](https://www.bartoszsypytkowski.com/operation-based-crdts-arrays-1/)) and trees. Unsurprisingly, text CRDTs are more complicated to implement than a grow-only set. [Cola](https://nomad.foo/blog/cola) is a text CRDT written in Rust, but the data structure can be implemented in any language.
|
||||
**Representing text in CRDTs**: Collaborative text documents can be represented using sequence CRDTs like lists (e.g. [Linear Sequences and Replicated Growable Array](https://www.bartoszsypytkowski.com/operation-based-crdts-arrays-1/)) and trees. Unsurprisingly, text CRDTs are more complicated to implement than a grow-only set. [Cola](https://nomad.foo/blog/cola) is a text CRDT written in Rust, but the data structure can be implemented in any language.
|
||||
|
||||
{/* TODO: give structure of list CRDT as sample */}
|
||||
|
||||
Text editing also involves deleting. How can we represent deletion in CRDTs? The trick is to track deletions as well by using two separate grow-only data structures, one to track insertion and another to track deletion (known as tombstone markers). The resulting value is the items in the insertion set minus the items in the deletion set. Complex CRDTs are often combined from smaller CRDTs which helps tremendously in preserving the CRDT properties.
|
||||
|
||||
##### **Analysis of CRDTs**
|
||||
|
||||
Let's take a look at how CRDTs fulfill the conflict resolution properties:
|
||||
**Analysis of CRDTs**: Let's take a look at how CRDTs fulfill the conflict resolution properties:
|
||||
|
||||
- **Convergence**: CRDT operations are designed to be commutative and associative, meaning the order and grouping in which operations are applied does not affect the final state. This is a key reason why all replicas converge to the same state.
|
||||
- **Causality preservation**: Operations are applied to other replicas in a way that respects causality. For example, operations can be propagated in any order, but an operation might be buffered until all preceding causally related operations have been applied. E.g. a deletion only takes effect until the insertion has taken place. In a Last-Writer-Wins Register (LWW-Register), updates are tagged with timestamps, ensuring that the most recent update (according to causality) prevails.
|
||||
- **Intention preservation**: CRDTs incorporate predefined conflict resolution strategies that aim to preserve user intent. These strategies are typically application-specific and ensure that the merged state reflects the combined intentions of all concurrent operations. The specific semantics of CRDT operations are crafted to ensure that, when two operations conflict, the resolution preserves the most meaningful aspects of each operation. However, there's some amount of subjectivity and is highly implementation and use-case dependent.
|
||||
|
||||
##### **Drawbacks of CRDTs**
|
||||
|
||||
While CRDTs are more modern compared to OT, it does come with some drawbacks:
|
||||
**Drawbacks of CRDTs**: While CRDTs are more modern compared to OT, it does come with some drawbacks:
|
||||
|
||||
- **Metadata overhead**: CRDTs often require additional metadata to track operations, revisions, or unique identifiers. This metadata can grow over time, leading to increased storage requirements, especially in large-scale systems or with complex data types.
|
||||
- **Ever-increasing size**: CRDTs have a monotonically increasing state, often having to track removals that do not appear in the final visual result. This means the data will only grow over time. Garbage collection or cleanup mechanisms can be used but they can be technically challenging to implement without causing inconsistencies in replicas.
|
||||
|
|
@ -458,9 +442,7 @@ When a user starts typing:
|
|||
4. If the local updates buffer is not empty, it only sends the next request after it receives a response for the current request from the server, even if the idle duration has passed
|
||||
5. This repeats until the local updates buffer is empty (when the user has stopped typing) and there are no more operations to send to the server
|
||||
|
||||
##### **Fast vs slow connections**
|
||||
|
||||
Since there can only be one pending update request, the frequency of requests is highly dependent on the client's connection speed.
|
||||
**Fast vs slow connections**: Since there can only be one pending update request, the frequency of requests is highly dependent on the client's connection speed.
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ We use offset-based pagination here as opposed to cursor-based pagination becaus
|
|||
1. Product results do not suffer from the stale results issue that much as new products are not added that quickly/frequently.
|
||||
1. It's useful to know how many total results there are.
|
||||
|
||||
For a more in-depth comparison between offset-based pagination and cursor-based pagination, refer to the [News Feed question](/questions/system-design/news-feed-facebook).
|
||||
For a more in-depth comparison between offset-based pagination and cursor-based pagination, refer to the [News Feed system design article](/questions/system-design/news-feed-facebook).
|
||||
|
||||
### Fetch product details
|
||||
|
||||
|
|
@ -415,7 +415,7 @@ SEO is extremely important for e-commerce websites as organic search is the prim
|
|||
- Use SSR for better SEO because search engines can index the content without having to wait for the page to be rendered (in the case of CSR).
|
||||
- Pre-generate pages for popular searches or lists.
|
||||
|
||||
The [Travel Booking (Airbnb) solution](/questions/system-design/travel-booking-airbnb) goes into more details about SEO.
|
||||
The [Travel Booking (Airbnb) system design article](/questions/system-design/travel-booking-airbnb) goes into more details about SEO.
|
||||
|
||||
### Images
|
||||
|
||||
|
|
|
|||
|
|
@ -162,9 +162,7 @@ The layout above assumes that all the images are of the same size. However, it i
|
|||
|
||||
Here are a few ways we can get around this:
|
||||
|
||||
##### **CSS `background-size`**
|
||||
|
||||
This requires a change in the HTML to render the image using CSS `background`/`background-image` instead of `<img src="...">`. The advantage of this is that we can use the `background-size` property that has these two modes:
|
||||
**CSS `background-size`**: This requires a change in the HTML to render the image using CSS `background`/`background-image` instead of `<img src="...">`. The advantage of this is that we can use the `background-size` property that has these two modes:
|
||||
|
||||
- `contain`: Scales the image as large as possible within its container without cropping or stretching the image. If the container is larger than the image, this will result in image tiling, unless the `background-repeat` property is set to `no-repeat`.
|
||||
- `cover`: Scales the image (while preserving its ratio) to the smallest possible size to fill the container (that is: both its height and width completely cover the container), leaving no empty space. If the proportions of the background differ from the element, the image is cropped either vertically or horizontally.
|
||||
|
|
@ -173,9 +171,7 @@ _Source: [background-size - CSS: Cascading Style Sheets | MDN](https://developer
|
|||
|
||||
Both advantages has its merits and which to use really depends on the provided images. One way is to allow the developer to customize whether to use `contain` or `cover` for all the images or even allowing customizing this setting for individual images.
|
||||
|
||||
##### **CSS `object-fit`**
|
||||
|
||||
CSS `object-fit` is a feature that is similar to how `background-size` works for `background-image`s, but for `<img>` and `<video>` HTML tags. It has `contain` and `cover` properties as well which work the same way as `background-size`'s version.
|
||||
**CSS `object-fit`**: CSS `object-fit` is a feature that is similar to how `background-size` works for `background-image`s, but for `<img>` and `<video>` HTML tags. It has `contain` and `cover` properties as well which work the same way as `background-size`'s version.
|
||||
|
||||
This way is preferred since it's more semantic to use `<img>` tags than `<div>`s with `background-image`s.
|
||||
|
||||
|
|
|
|||
|
|
@ -462,9 +462,7 @@ In Stephen Curry's post above, see that he used the "#AboutLastNight" hashtag an
|
|||
|
||||
What format should the message be so that it can store data about mentions/hashtags? Let's discuss the possible formats and their pros and cons.
|
||||
|
||||
##### **HTML format**
|
||||
|
||||
The simplest format is HTML, you store the message the way you want it to be displayed.
|
||||
**HTML format**: The simplest format is HTML, you store the message the way you want it to be displayed.
|
||||
|
||||
```md
|
||||
<a href="...">#AboutLastNight</a> is here... and ready to change the meaning of date night...
|
||||
|
|
@ -474,18 +472,14 @@ Absolute comedy 🤣 Dropping 2/10 on <a href="...">HBO Max</a>!
|
|||
|
||||
Storing as HTML is usually bad because there's a potential for causing a Cross-Site Scripting (XSS) vulnerability. Also, in most cases it's better to decouple the message's metadata from displaying, perhaps in future you want to decorate the mentions/hashtags before rendering and want to add classnames to the links. HTML format also makes the API less reusable on non-web clients (e.g. iOS/Android).
|
||||
|
||||
##### **Custom syntax**
|
||||
|
||||
A custom syntax can be used to capture metadata about hashtags and mentions.
|
||||
**Custom syntax**: A custom syntax can be used to capture metadata about hashtags and mentions.
|
||||
|
||||
- **Hashtags**: Hashtags don't actually need a special syntax, words that start with a "#" can be considered a hashtag.
|
||||
- **Mentions**: A syntax like `[[#1234: HBO Max]]` is sufficient to capture the entity ID and the text to display. It's not sufficient to just store the entity ID because sites like Facebook allow users to customize the text within the mention.
|
||||
|
||||
Before rendering the message, the string can be parsed for hashtags and mentions using regex and replaced with custom-styled links. Custom syntax is a lightweight solution which is robust enough if you don't anticipate new kinds of rich text entities to be supported in future.
|
||||
|
||||
##### **Popular rich text editor format**
|
||||
|
||||
[Draft.js](https://draftjs.org) is a popular rich text editor by Facebook for composing rich text. Draft.js allows users to extend the functionality and create their own rich text entities such as hashtags and mentions. It defines a custom Draft.js editor state format which is being used by the Draft.js editor. In 2022, Facebook released [Lexical](https://lexical.dev/), a successor to Draft.js and is using Lexical for rich text editing and displaying rich text entities on facebook.com. The underlying formats are similar and we will discuss Draft.js' format.
|
||||
**Rich text editor format**: [Draft.js](https://draftjs.org) is a popular rich text editor by Meta for composing rich text. Draft.js allows users to extend the functionality and create their own rich text entities such as hashtags and mentions. It defines a custom Draft.js editor state format which is being used by the Draft.js editor. In 2022, Meta released [Lexical](https://lexical.dev/), a successor to Draft.js and is using Lexical for rich text editing and displaying rich text entities on facebook.com. The underlying formats are similar and we will discuss Draft.js' format.
|
||||
|
||||
Draft.js is just one example of a rich text format, there are many out there to choose from. The editor state resembles an Abstract Syntax Tree and can be serialized into a JSON string to be stored. The benefits of using a popular rich text format is that you don't have to write custom parsing code and can easily extend to more types of rich text entities in future. However, these formats tend to be longer strings than a custom syntax version and will result in larger network payload size and require more disk space to store.
|
||||
|
||||
|
|
@ -557,13 +551,11 @@ Optimistic updates is a powerful feature built into modern query libraries like
|
|||
|
||||
Timestamp rendering is a topic worth discussing because of a few issues: multilingual timestamps and stale relative timestamps.
|
||||
|
||||
##### **Multilingual timestamps**
|
||||
|
||||
Globally popular sites like Facebook and Twitter have to ensure their UI works well for different languages. There are a few ways to support multilingual timestamps:
|
||||
**Multilingual timestamps**: Globally popular sites like Facebook and Twitter have to ensure their UI works well for different languages. There are a few ways to support multilingual timestamps:
|
||||
|
||||
1. **Server returns the raw timestamp**: Server returns the raw timestamp and the client renders in the user's language. This approach is flexible but requires the client to contain the grammar rules and translation strings for different languages, which can amount to significant JavaScript size depending on the number of supported languages,
|
||||
1. **Server returns the translated timestamp**: This requires processing on the server, but you don't have to ship timestamp formatting rules for various languages to the client. However, since translation is done on the server, clients cannot manipulate the timestamp on the client.
|
||||
1. **`Intl` API**: Modern browsers can leverage `Intl.DateTimeFormat()` and `Intl.RelativeTimeFormat()` to transform a raw timestamp into translated datetime strings in the desired format.
|
||||
2. **Server returns the translated timestamp**: This requires processing on the server, but you don't have to ship timestamp formatting rules for various languages to the client. However, since translation is done on the server, clients cannot manipulate the timestamp on the client.
|
||||
3. **`Intl` API**: Modern browsers can leverage `Intl.DateTimeFormat()` and `Intl.RelativeTimeFormat()` to transform a raw timestamp into translated datetime strings in the desired format.
|
||||
|
||||
```js
|
||||
const date = new Date(Date.UTC(2021, 11, 20, 3, 23, 16, 738));
|
||||
|
|
@ -582,9 +574,7 @@ console.log(
|
|||
); // 1天前
|
||||
```
|
||||
|
||||
##### **Relative timestamps can turn stale**
|
||||
|
||||
If timestamps are displayed using a relative format (e.g. 3 minutes ago, 1 hour ago, 2 weeks ago, etc), recent timestamps can easily go stale especially for applications where users don't refresh the page. A timer can be used to constantly update the timestamps if they are recent (less than an hour old) such that any significant time that has passed will be reflected correctly.
|
||||
**Relative timestamps can turn stale**: If timestamps are displayed using a relative format (e.g. 3 minutes ago, 1 hour ago, 2 weeks ago, etc), recent timestamps can easily go stale especially for applications where users don't refresh the page. A timer can be used to constantly update the timestamps if they are recent (less than an hour old) such that any significant time that has passed will be reflected correctly.
|
||||
|
||||
#### Icon rendering
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
**Note**: This application shares many similarities with the [News Feed](/questions/system-design/news-feed-facebook) application and the image carousels within the feed post will benefit from the [Image Carousel solution](/questions/system-design/image-carousel). Please have a read of that question before starting on this. For this question, we will focus on things that haven't been covered, the discussion will be centered around the photos and images.
|
||||
**Note**: This application shares many similarities with the [News Feed system design](/questions/system-design/news-feed-facebook) application and the image carousels within the feed post will benefit from the [Image Carousel system design](/questions/system-design/image-carousel). Please have a read of that question before starting on this. For this question, we will focus on things that haven't been covered, the discussion will be centered around the photos and images.
|
||||
|
||||
## Requirements exploration
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ Photo sharing applications have the following characteristics:
|
|||
- Application is interaction heavy due to post composing and liking/commenting functionality on posts.
|
||||
- Fast initial loading speed is desired.
|
||||
|
||||
These are also characteristics of a [News Feed](/questions/system-design/news-feed-facebook) where there's a good amount of static content which also require interaction. Hence a combination of server-side rendering with hydration and subsequent client-side rendering would be ideal. In reality, [instagram.com](https://instagram.com) is built using the same technology stack as facebook.com which uses React server-side rendering with hydration.
|
||||
These are also characteristics of a [News Feed system design](/questions/system-design/news-feed-facebook) where there's a good amount of static content which also require interaction. Hence a combination of server-side rendering with hydration and subsequent client-side rendering would be ideal. In reality, instagram.com is built using the same technology stack as facebook.com which uses React server-side rendering with hydration.
|
||||
|
||||
### Architecture diagram
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ A photo feed shows a list of image posts fetched from the server, hence most of
|
|||
| `NewPost` | User input (Client) | Post composer UI | `caption`, `images` (`Image`) |
|
||||
| `Image` | Server/client | Multiple | `url`, `alt`, `width`, `height` |
|
||||
|
||||
Just like in the [News Feed](/questions/system-design/news-feed-facebook), a normalized client-side store will also be useful for a Photo Sharing application.
|
||||
Just like in the [News Feed system design](/questions/system-design/news-feed-facebook), a normalized client-side store will also be useful for a Photo Sharing application.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ Comparing the two approaches, approach #2 of separate APIs would be better. The
|
|||
|
||||
### Feed optimizations
|
||||
|
||||
These optimizations done for [News Feed](/questions/system-design/news-feed-facebook) also apply to the feed of photo sharing applications.
|
||||
These optimizations done in [News Feed system design](/questions/system-design/news-feed-facebook) also apply to the feed of photo sharing applications.
|
||||
|
||||
- Rendering approach
|
||||
- Infinite scrolling implementation
|
||||
|
|
@ -134,7 +134,7 @@ These optimizations done for [News Feed](/questions/system-design/news-feed-face
|
|||
|
||||
### Image carousel optimizations
|
||||
|
||||
The images within a post are shown as a carousel, so the [Image Carousel](/questions/system-design/image-carousel) implementation is also useful for this question and the same optimizations apply.
|
||||
The images within a post are shown as a carousel, so the [Image Carousel system design article](/questions/system-design/image-carousel) is also useful for this question and the same optimizations apply.
|
||||
|
||||
### Rendering images
|
||||
|
||||
|
|
|
|||
|
|
@ -20,4 +20,4 @@ Pinterest front end system design questions are commonly asked in two manners:
|
|||
|
||||
We will focus on the former but also provide enough content and guidance for the latter. In fact, the actual masonry component Pinterest uses on their homepage is built in [React and open sourced](https://gestalt.pinterest.systems/web/masonry)! You can dive into the source code to know the ins and outs of the component and also use it on your own sites.
|
||||
|
||||
**Note**: Pinterest is essentially an image feed with a multi-column layout. Hence it shares many similarities with the [News Feed question](/questions/system-design/news-feed-facebook) and the [Instagram question](/questions/system-design/photo-sharing-instagram). Please have a read of those questions before starting on this. For this question, the discussion will be centered around the masonry layout and less about general feed optimizations.
|
||||
**Note**: Pinterest is essentially an image feed with a multi-column layout. Hence it shares many similarities with the [News Feed system design](/questions/system-design/news-feed-facebook) and [Instagram system design](/questions/system-design/photo-sharing-instagram). Please have a read of those questions before starting on this. For this question, the discussion will be centered around the masonry layout and less about general feed optimizations.
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ The homepage feed shows a list of pins fetched from the server, hence most of th
|
|||
|
||||
Although the `Feed` and `Pin` entities belong to the homepage, all server-originated data can be stored in the client store and queried by components which need them. E.g. for a pin details page, assuming no additional data is needed, it can read the pin details from the client store and display additional details such as the author, title, description.
|
||||
|
||||
The shape of the client store is not particularly important here, as long as it is in a format that can be easily retrieved from the components. A normalized store like the one mentioned in [News Feed](/questions/system-design/news-feed-facebook#advanced-normalized-store) will allow efficient lookup via the pin ID. New pins fetched from the second page should be joined with the previous pins into a single list with the pagination parameters (`cursor`) updated.
|
||||
The shape of the client store is not particularly important here, as long as it is in a format that can be easily retrieved from the components. A normalized store like the one mentioned in [News Feed system design](/questions/system-design/news-feed-facebook#advanced-normalized-store) will allow efficient lookup via the pin ID. New pins fetched from the second page should be joined with the previous pins into a single list with the pagination parameters (`cursor`) updated.
|
||||
|
||||
### Pinterest-specific data
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ Alternatively, log in to Pinterest and visit "https://www.pinterest.com/resource
|
|||
|
||||
#### Pagination approach
|
||||
|
||||
For infinite scrolling feeds there isn't a need to jump to a specific page, hence cursor-based pagination can be used here for reasons similar to [News Feed](/questions/system-design/news-feed-facebook#cursor-based-pagination).
|
||||
For infinite scrolling feeds there isn't a need to jump to a specific page, hence cursor-based pagination can be used here for reasons similar to [News Feed system design](/questions/system-design/news-feed-facebook#cursor-based-pagination).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -181,7 +181,7 @@ A plausible heuristic is to fetch around two to three screens worth of pins each
|
|||
|
||||
#### Infinite scrolling
|
||||
|
||||
The common ways to implement infinite scrolling has been covered in [News Feed](/questions/system-design/news-feed-facebook#infinite-scrolling).
|
||||
The common ways to implement infinite scrolling has been covered in [News Feed system design](/questions/system-design/news-feed-facebook#infinite-scrolling).
|
||||
|
||||
### Media loading
|
||||
|
||||
|
|
@ -300,9 +300,7 @@ There are two popular ways to achieve a masonry layout on the web:
|
|||
1. Rows of columns
|
||||
2. Absolute positioning
|
||||
|
||||
##### **Row of columns**
|
||||
|
||||
This method involves rendering equal-width columns then placing items within each column. This approach heavily leverages the browser for positioning.
|
||||
**Row of columns**: This method involves rendering equal-width columns then placing items within each column. This approach heavily leverages the browser for positioning.
|
||||
|
||||
The pros of this approach are that it is easy to implement as it leverages `display: flex` and the required CSS is not very complex. If the height of any item changes, the positions of the items within that column will be updated automatically.
|
||||
|
||||
|
|
@ -354,9 +352,7 @@ The following example is implemented using the "row of columns" layout and the n
|
|||
|
||||
---
|
||||
|
||||
##### **Absolute positioning**
|
||||
|
||||
A better approach is to calculate exactly where the pins should be positioned within a `position: relative` parent container and place them exactly where they should be via `position: absolute; top: Y; left: X`.
|
||||
**Absolute positioning**: A better approach is to calculate exactly where the pins should be positioned within a `position: relative` parent container and place them exactly where they should be via `position: absolute; top: Y; left: X`.
|
||||
|
||||
The following diagram demonstrates the `top` and `left` CSS style values of the items for a 3-column layout container.
|
||||
|
||||
|
|
@ -418,9 +414,7 @@ There are two common ways to order the pins:
|
|||
1. Round robin
|
||||
2. Height balancing
|
||||
|
||||
##### **Round robin placement**
|
||||
|
||||
In round robin placement, we put the pins in order into each column, wrapping around to the first column after the last, until all pins have been placed. In a three-column layout, pins are placed in these respective columns:
|
||||
**Round robin placement**: In round robin placement, we put the pins in order into each column, wrapping around to the first column after the last, until all pins have been placed. In a three-column layout, pins are placed in these respective columns:
|
||||
|
||||
| Column | Pins position (within feed) |
|
||||
| ------ | --------------------------- |
|
||||
|
|
@ -448,9 +442,7 @@ The benefit of this approach is that the algorithm runs in O(N) and is easy to i
|
|||
|
||||
However, this approach does not factor in the items height at all. As a result, some columns might end up taller than others. Taking the above example, the first column contains a few tall items and ends up being substantially taller than the second column. The result might not be visually pleasing.
|
||||
|
||||
##### **Height balancing**
|
||||
|
||||
In height balancing placement, the pins are placed within the shortest column, at the time of arrangement.
|
||||
**Height balancing**: In height balancing placement, the pins are placed within the shortest column, at the time of arrangement.
|
||||
|
||||
For a three-column layout, we can use an array of size 3 (`columnHeights`) where the value at the index represents the running total height of items in that column. For each pin, by looping through the `columnHeights` array once, we can determine the shortest column. Next, we calculate the pin's position based on the left-most column with the shortest current height and update that column's height for the newly-added pin.
|
||||
|
||||
|
|
@ -515,9 +507,7 @@ The result is a layout that the columns have generally balanced heights, which i
|
|||
|
||||
This `columnHeights` computation should be preserved within the Masonry component as state so that when the next page of pins is loaded and need to be arranged on the page, the column heights are immediately known, there's no need to tally the height of the columns again or arrange the pins that are already on-screen.
|
||||
|
||||
##### **Server computations**
|
||||
|
||||
Currently, the computations are done on the client. For Pinterest, although SSR is being used, the initial HTML for the pins does not contain positioning data and the positions are calculated on the client. This is probably because the server does not know the viewport dimensions of the client.
|
||||
**Server computations**: Currently, the computations are done on the client. For Pinterest, although SSR is being used, the initial HTML for the pins does not contain positioning data and the positions are calculated on the client. This is probably because the server does not know the viewport dimensions of the client.
|
||||
|
||||
Pinterest grids have a few breakpoint widths for varying number of columns, so the total width of the container for each number of column is already known beforehand. It's technically possible to calculate the pins' positions on the server for all possible breakpoints and send them down to the client. The client just has to pick the pre-calculated values for the current breakpoint.
|
||||
|
||||
|
|
@ -725,7 +715,7 @@ The focus of this question is on the feed masonry layout and we have intentional
|
|||
- **Pin creation**. Uploading images and filling in essential information like title and description.
|
||||
- **Extra actions only revealed upon interaction**. Lazy load the code for these non-essential actions only when they're being used or when the page is idle.
|
||||
|
||||
These topics are covered in detail in [News Feed](/questions/system-design/news-feed-facebook).
|
||||
These topics are covered in detail in [News Feed system design](/questions/system-design/news-feed-facebook).
|
||||
|
||||
## References
|
||||
|
||||
|
|
|
|||
|
|
@ -256,9 +256,7 @@ The server APIs are ideally served on the same domain so that there is no need t
|
|||
|
||||
What format should the API return the results in?
|
||||
|
||||
##### **1. Return the options and the counts for each**
|
||||
|
||||
The server returns something like:
|
||||
**1. Return the options and the counts for each**: The server returns something like:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -294,9 +292,7 @@ The server returns something like:
|
|||
- Server has to do the processing, but likely better because server can cache/memoize the results especially for popular polls and return the cached results for different users.
|
||||
- The server will need to update the tabulation every now and then. This is a good use case for in-memory key/value stores like Redis/Memcached.
|
||||
|
||||
##### **2. Raw list of poll responses**
|
||||
|
||||
The server returns something like:
|
||||
\*_2. Raw list of poll responses_: The server returns something like:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -339,7 +335,7 @@ The server returns something like:
|
|||
- Doesn't scale well when there are lots of responses, the network payload will be huge.
|
||||
- The client needs to do the tabulation of votes which can be expensive on low-end devices and when there are many votes.
|
||||
|
||||
##### **Which to choose?**
|
||||
**Which to choose?**
|
||||
|
||||
Directly returning the counts is usually the superior option as there is usually no need to do tabulation or manipulation of the data on the client, which does not scale for popular votes with tens of thousands of votes.
|
||||
|
||||
|
|
@ -386,16 +382,14 @@ The first option is the more common option and is used by Reddit and Twitter.
|
|||
|
||||
The number of votes for an option over the total number of votes will be the proportion of the full width to render the bar. E.g. 400 votes for an option over a total of 1000 votes will mean that the bar should be rendered 40% of the full width. Since there are infinitely many possible values for the width, it is not practical to use static CSS classes to render a bar for a specific width. A better way would be to use inline styles that are dynamically generated during rendering.
|
||||
|
||||
##### **1. Using `width` style**
|
||||
|
||||
This is the most common approach and the only small downside is that in case there's a need for animating of the bars expanding/contracting, animation of `width` property is slower than `transform`. However, the widget is mostly static so the animating issue is largely not present.
|
||||
**1. Using CSS `width` inline style**: This is the most common approach and the only small downside is that in case there's a need for animating of the bars expanding/contracting, animation of `width` property is slower than `transform`. However, the widget is mostly static so the animating issue is largely not present.
|
||||
|
||||
```html
|
||||
<div style="width: 40%">React</div>
|
||||
<div style="width: 30%">Vue</div>
|
||||
```
|
||||
|
||||
##### **2. Using `transform: scaleX()` style**
|
||||
**2. Using `transform: scaleX()` style**: This method involves scaling the element horizonally.
|
||||
|
||||
```html
|
||||
<div style="transform: scaleX(40%)">React</div>
|
||||
|
|
@ -406,7 +400,7 @@ Note that `scaleX()` will also transform contents within it and make them appear
|
|||
|
||||
We should use a % of the full width of the widget instead of hardcoded px values calculated once when the page loads so that if the widget is resized, the bars' width will be updated. % widths can be achieved using `width` and `transform: scaleX()`.
|
||||
|
||||
If we are fine with some loss of precision, then we could have 101 classnames for percentages of 0 to 100. But in general, this is not a great approach and inline styles is the preferred approach.
|
||||
If we are fine with some precision loss, then we could have 101 classnames for percentages of 0 to 100. But in general, this is not a great approach and inline styles is the preferred approach.
|
||||
|
||||
### User experience
|
||||
|
||||
|
|
|
|||
|
|
@ -363,9 +363,7 @@ interface TextNode extends EditorNode {
|
|||
|
||||
With a structure resembling the DOM, we are able to represent all sorts of possible rich text content and formatting.
|
||||
|
||||
##### **Tree representation**
|
||||
|
||||
How are these nodes represented within `EditorState`? An intuitive way is to mirror the resulting DOM in JavaScript – a 1:1 tree representation by using an `ElementNode` subclass as the root node.
|
||||
**Tree representation**: How are these nodes represented within `EditorState`? An intuitive way is to mirror the resulting DOM in JavaScript – a 1:1 tree representation by using an `ElementNode` subclass as the root node.
|
||||
|
||||
```ts
|
||||
{
|
||||
|
|
@ -390,9 +388,7 @@ How are these nodes represented within `EditorState`? An intuitive way is to mir
|
|||
|
||||
Many rich text editors (e.g. Slate.js, Draft.js) use this tree-like structure. It works, but trees aren't the most efficient data structure for frequent updates. For one, trees do not provide efficient access to its nodes; accessing deep leaf nodes would require traversal of the tree starting from the root.
|
||||
|
||||
##### **Map with child pointers**
|
||||
|
||||
How about using a `Map` of `EditorNode`s that contains pointers to other nodes?
|
||||
**Map with child pointers**: How about using a `Map` of `EditorNode`s that contains pointers to other nodes?
|
||||
|
||||
```ts
|
||||
type NodeKey = string;
|
||||
|
|
@ -417,9 +413,7 @@ This `NodeMap` gives the same tree as before:
|
|||
|
||||
Using this structure, obtaining a reference to the `EditorNode`s is a matter of doing `nodeMap.get(nodeKey)`.
|
||||
|
||||
##### **Map with children as linked list**
|
||||
|
||||
Lexical started with using a `Map` of `EditorNode`s with a `children` pointer array. Eventually it moved to doubly linked lists (instead of arrays) for its child node pointers as a performance optimization along with parent pointers. All `EditorNode`s can have siblings and parents. `ElementNode`s have additional references to their first and last child, if they exist.
|
||||
**Map with children as linked list**: Lexical started with using a `Map` of `EditorNode`s with a `children` pointer array. Eventually it moved to doubly linked lists (instead of arrays) for its child node pointers as a performance optimization along with parent pointers. All `EditorNode`s can have siblings and parents. `ElementNode`s have additional references to their first and last child, if they exist.
|
||||
|
||||
```ts
|
||||
type NodeKey = string;
|
||||
|
|
@ -475,9 +469,7 @@ One tricky issue with formatting text in the DOM is when characters have more th
|
|||
- "and" is both bolded and underlined
|
||||
- "Jane" is underlined
|
||||
|
||||
##### **Nested tags**
|
||||
|
||||
In HTML/DOM, there are multiple ways to achieve this formatting. Here are some possible ways:
|
||||
**Nested tags**: In HTML/DOM, there are multiple ways to achieve this formatting. Here are some possible ways:
|
||||
|
||||
- `<strong>Tarzan </strong><u><strong>and</strong> Jane</u>`
|
||||
- `<strong>Tarzan <u>and</u></strong><u> Jane</u>`
|
||||
|
|
@ -489,9 +481,7 @@ The first two ways use nested tags for formatting and it can become a problem wh
|
|||
|
||||
`contenteditable` is able to handle the formatting correctly, but the resulting HTML is messy and not optimized.
|
||||
|
||||
##### **Format ranges**
|
||||
|
||||
Another way is to use format ranges (not to be confused with selection range), where each format is labeled with start/end indices. **Tarzan <u>and</u>** <u>Jane</u> will be:
|
||||
**Format ranges**: Another way is to use format ranges (not to be confused with selection range), where each format is labeled with start/end indices. **Tarzan <u>and</u>** <u>Jane</u> will be:
|
||||
|
||||
```js
|
||||
[
|
||||
|
|
@ -502,9 +492,7 @@ Another way is to use format ranges (not to be confused with selection range), w
|
|||
|
||||
This list-like format is definitely convenient and more readable. But ultimately, to be rendered to the DOM, the list has to first be converted into element and text nodes. Updates to text will require computation for updating indices correctly.
|
||||
|
||||
##### **Lexical's formatting approach**
|
||||
|
||||
Let's take a closer look at the last option in the HTML approach:
|
||||
**Lexical's formatting approach**: Let's take a closer look at the last option in the HTML approach:
|
||||
|
||||
```jsx
|
||||
<strong>Tarzan </strong><strong><u>and</u></strong><u> Jane</u>
|
||||
|
|
@ -1176,7 +1164,7 @@ Real-time collaborative editing is complicated because of the following complexi
|
|||
|
||||
Real-time collaborative editing is beyond the scope of rich text editor system design, but Lexical's core model + commands architecture allows for real-time collaboration functionality; the collaboration layer can be built beneath the existing editor core and dispatch commands for the editor core to execute.
|
||||
|
||||
For a deep dive on collaborative editing, we recommend reading our entire [system design article around collaborative editing](/questions/system-design/collaborative-editing-google-docs).
|
||||
For a deep dive on collaborative editing, we recommend reading our entire [Collaborative Editor system design article](/questions/system-design/collaborative-editing-google-docs).
|
||||
|
||||
Operational Transformations (OT) and Conflict-free Replicated Data Types (CRDTs) are ways of handling simultaneous conflicts in documents. OT resolves conflicts by transforming user operations against peer operations by accounting for the state of the document when the operation was made. CRDTs avoid conflicts altogether with specially-designed data structures so that updates can be made in any order and clients will still eventually converge into a consistent state.
|
||||
|
||||
|
|
@ -1213,7 +1201,7 @@ Here are some key accessibility considerations for rich text editors, including
|
|||
- **Live regions**: Use ARIA live regions to announce dynamic changes, such as formatting changes or error messages.
|
||||
- **Labels and instructions**: Provide clear labels and instructions for all editor controls and ensure they are announced by screen readers. Icon-only buttons should use `aria-label`s.
|
||||
|
||||
##### **Visible focus indicators**
|
||||
#### Visible focus indicators
|
||||
|
||||
Ensure that all interactive elements, including the editor itself and toolbar buttons, have visible focus indicators. This helps users who rely on the keyboard to see where the focus is.
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ From the above, we can gather that the most important aspects of the website are
|
|||
- **Internationalization**: The users are from many different countries and from various age groups. To capture a larger market, the website should be translated and localized.
|
||||
- **Device support**: The site should work well for a variety of devices since the user demographics is very broad.
|
||||
|
||||
The requirements and nature of a travel booking website are very similar to the [e-commerce website question](/questions/system-design/e-commerce-amazon), so there is a high degree of overlap between the solutions. In this solution we will cover the unique aspects of travel booking websites which aren't found on e-commerce websites.
|
||||
The requirements and nature of a travel booking website are very similar to [E-commerce website system design](/questions/system-design/e-commerce-amazon), so there is a high degree of overlap between the solutions. In this solution we will cover the unique aspects of travel booking websites which aren't found on e-commerce websites.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ The requirements and nature of a travel booking website are very similar to the
|
|||
|
||||
### Server-side rendering (SSR) or Client-side rendering?
|
||||
|
||||
Like [e-commerce websites](/questions/system-design/e-commerce-amazon), performance and SEO are critical. Hence server-side rendering is a must because of the performance and SEO benefits.
|
||||
Like [E-commerce website system design](/questions/system-design/e-commerce-amazon), performance and SEO are critical. Hence server-side rendering is a must because of the performance and SEO benefits.
|
||||
|
||||
### Single-page application (SPA) or Multi-page application (MPA)?
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ For simplicity, and since SPAs are covered commonly in other questions, the disc
|
|||
| `ListingResults` | Server | Search/Listing Page | `results` (list of `ListingItem`s), `pagination` (pagination metadata) |
|
||||
| `ListingItem` | Server | Search/Listing Page, Details Page | `title`, `price`, `currency`, `image_urls`, `amenities` (flexible format) |
|
||||
|
||||
We have omitted the entities related to payments and addresses since they are covered in the [e-commerce question](/questions/system-design/e-commerce-amazon). If the interviewer wants you to focus on the checkout flow as well, do mention those entities.
|
||||
We have omitted the entities related to payments and addresses since they are covered in [E-commerce website system design](/questions/system-design/e-commerce-amazon). If the interviewer wants you to focus on the checkout flow as well, do mention those entities.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -120,23 +120,19 @@ We will need the following HTTP APIs:
|
|||
| Date Range | mixed (see below) | Date range to occupy the accommodation |
|
||||
| Amenities | mixed (see below) | Amenities criteria |
|
||||
|
||||
##### **Location**
|
||||
|
||||
The exact format of the `location` field depends on the business requirements. But in general, we need to tell the server the desired boundary so that results within the boundary will be shown. There are two common ways to represent boundaries: (1) Center position + radius, (2) Boundary coordinates.
|
||||
**Location**: The exact format of the `location` field depends on the business requirements. But in general, we need to tell the server the desired boundary so that results within the boundary will be shown. There are two common ways to represent boundaries: (1) Center position + radius, (2) Boundary coordinates.
|
||||
|
||||
1. **Center position + radius**. The radius can be in miles/km and the center position can be either of:
|
||||
- **Free text search**: This is any string containing a location e.g. "San Francisco", "New York City". An external location service will be needed to convert the string into geographic coordinates (via geocoding). Geocoding is preferably done on the server to prevent API abuse.
|
||||
- **Geolocation/Coordinates**: This format is useful for map-based UIs where users can pan/zoom the map to search for listings within the area or when users allow the app to access current location to search around them.
|
||||
1. **Boundary coordinates**. For map-based UIs, we can use the coordinates of the 4 corners of the presented map as the boundary area.
|
||||
2. **Boundary coordinates**. For map-based UIs, we can use the coordinates of the 4 corners of the presented map as the boundary area.
|
||||
|
||||
It's likely that the search API will have to support both formats if the website has different search UIs:
|
||||
|
||||
- Landing pages for travel sites usually have a search bar with the compulsory parameters like date range and number of guests. Such UI will need the free text search location format.
|
||||
- Results pages with maps will use the geolocation/boundary coordinates format.
|
||||
|
||||
##### **Date Range**
|
||||
|
||||
There are a number of date range formats to choose from and neither are clear winners:
|
||||
**Date Range**: There are several date range formats to choose from and neither are clear winners:
|
||||
|
||||
| Format | Example | Pros | Cons |
|
||||
| --- | --- | --- | --- |
|
||||
|
|
@ -146,9 +142,7 @@ There are a number of date range formats to choose from and neither are clear wi
|
|||
|
||||
The array format is the least clear out of the three, and the object requires encoding to be used in `GET`, so separate query parameters would be the most recommended.
|
||||
|
||||
##### **Amenities**
|
||||
|
||||
Putting the query parameters for amenities has the same issues with date range.
|
||||
**Amenities**: Putting the query parameters for amenities has the same issues with date range.
|
||||
|
||||
| Format | Example | Pros | Cons |
|
||||
| --- | --- | --- | --- |
|
||||
|
|
@ -203,7 +197,7 @@ We use offset-based pagination here as opposed to cursor-based pagination becaus
|
|||
1. Accommodations results do not suffer from the stale results issue that much because new listings are not added that quickly/frequently.
|
||||
1. It's useful to know how many total results there are.
|
||||
|
||||
For a more in-depth comparison between offset-based pagination and cursor-based pagination, refer to the [News Feed question](/questions/system-design/news-feed-facebook).
|
||||
For a more in-depth comparison between offset-based pagination and cursor-based pagination, refer to the [News Feed system design article](/questions/system-design/news-feed-facebook).
|
||||
|
||||
If infinite scroll is desired, then a cursor-based pagination approach might be required. Offset-based pagination can still be used, but the client will need to go through the trouble of filtering out duplicated results.
|
||||
|
||||
|
|
@ -369,16 +363,16 @@ Performance is known to affect conversions, so for websites where the aim is to
|
|||
|
||||
#### Image optimizations
|
||||
|
||||
- **Image carousel**: Photos are widely used on travel websites to showcase how attractive the destination/accommodation is. We have covered implementation of [Image Carousels](/questions/system-design/image-carousel) in a separate system design question.
|
||||
- **Image carousel**: Photos are widely used on travel websites to showcase how attractive the destination/accommodation is. We have covered implementation of image carousels in the [Image Carousel system design article](/questions/system-design/image-carousel).
|
||||
- **Image preloading/lazy loading**: One super useful technique is to use JavaScript to have fine-grain control when images load.
|
||||
- [Airbnb optimized the image carousel experience in their room listings with a combination of lazy loading and preloading](https://medium.com/airbnb-engineering/building-a-faster-web-experience-with-the-posttask-scheduler-276b83454e91) behavior:
|
||||
1. Initially, only the first image is loaded (the remaining images will be lazily loaded).
|
||||
1. The second image is preloaded when the user shows possible intent of viewing more images:
|
||||
2. The second image is preloaded when the user shows possible intent of viewing more images:
|
||||
- Cursor hovers over the image carousel.
|
||||
- Focuses on the "Next" button via tabbing.
|
||||
- Image carousel comes into view (on mobile devices).
|
||||
1. If the user does view the second image (which signals high intent to browse even more images), the next three images (3rd to 5th) are preloaded.
|
||||
1. As the user clicks "Next" again to browse more images, the (n + 3)<sup>th</sup> image is preloaded.
|
||||
3. If the user does view the second image (which signals high intent to browse even more images), the next three images (3rd to 5th) are preloaded.
|
||||
4. As the user clicks "Next" again to browse more images, the (n + 3)<sup>th</sup> image is preloaded.
|
||||
- Other resources: [Yes I'm Lazy | TripAdvisor Engineering and Product Blog](https://www.tripadvisor.com/engineering/yes-im-lazy/)
|
||||
|
||||
<figure>
|
||||
|
|
@ -468,7 +462,7 @@ The alternative is to open the listing details within a full-screen modal on the
|
|||
|
||||
### Form optimizations
|
||||
|
||||
Form optimizations have been covered in detail in the [e-commerce websites question](/questions/system-design/e-commerce-amazon). To recap:
|
||||
Form optimizations have been covered in detail in [E-commerce website system design](/questions/system-design/e-commerce-amazon). To recap:
|
||||
|
||||
- Country-specific address/payment Forms.
|
||||
- Optimize autofill experience.
|
||||
|
|
@ -484,4 +478,4 @@ Form optimizations have been covered in detail in the [e-commerce websites quest
|
|||
- [Building and scaling different travel websites with one codebase | Agoda Engineering & Design](https://medium.com/agoda-engineering/building-and-scaling-different-travel-websites-with-one-codebase-fc6f0202c2e1)
|
||||
- [Managing and scaling different white label development and testing environments | Agoda Engineering & Design](https://medium.com/agoda-engineering/managing-and-scaling-different-white-label-development-and-testing-environments-4e90748fcb3b)
|
||||
- Testing
|
||||
- [Lowering the Noise Floor | | TripAdvisor Engineering and Product Blog](https://www.tripadvisor.com/engineering/lowering-the-noise-floor/)
|
||||
- [Lowering the Noise Floor | TripAdvisor Engineering and Product Blog](https://www.tripadvisor.com/engineering/lowering-the-noise-floor/)
|
||||
|
|
|
|||
Loading…
Reference in New Issue