- Image Carousel
- Selector which loads options over the network
- Requirements clarifications/alignment
- Data model
- API design
- Deep dive
- User Experience (UX)
- Accessibility (a11y)
- Internationalization (i18n)
- Multi-device support
Every system design interview should start with clarifying requirements about the question, which is usually left vague intentionally. Components have well-defined scope and not try to do too many things.
- What devices should the system support? Desktop web, mobile web, etc
- What's the primary device that users will access the system on?
- Which browsers should we support?
- Do we need to support internationalization?
- How much styling customization do we want to allow?
Architecture for front end interviews are typically focused on the client-side architecture, and not on large scale distributed systems where databases, load balancers and servers are involved.
For components, list down the various sub-components that will exist within it, what data is being passed among each component (e.g. in a Photo Gallery, there are the Image, Thumbnail, Paginator, etc sub-components). If you have a whiteboard/online drawing tool, it would also be helpful to draw diagrams to illustrate the entities and their relationships.
Data model for components will refer to the component state. The concept of state should be familiar to most front end developers who have used front end UI libraries/frameworks such as React, Angular, Vue, Svelte, etc. In every of these libraries/frameworks, state is a common concept.
Deciding what data to put in state is essential to doing well for this portion. Few factors to consider when deciding what goes into component state:
- State is allowed to change over time during the lifecycle of the component, typically as a result of user interactions
- Each component has its own state which allows multiple instances of the component to coexist on a single page. The state of a component instance should not affect the state of another instance
- Components are easier to reason about (read/understand) the less state there is. We should strive to reduce the amount of state needed. If a component uses a value which can be derived from another piece of state, then that value should most likely not be part of the state. For example if your component is rendering a list of items and you want to display a message when there are no items to render, there shouldn't be an additional
isEmptystate because it can be derived from the length of the
- If a component has multiple sub-components, it'll be best if it's possible to consolidate the state within the top level and the rest of the components are pure and stateless
The key idea behind components is for reusing. Good components are designed well such that they can be reused in multiple scenarios.
- Configuration options for the component (
- Follow Open-closed principle - the component should be open for extension but closed for modification. In React,
- If your component is meant to be a UI library that doesn't bother about the appearance and leaves the styling to the user, extra care has to go into the design of the props and to allow users to customize the look and feel of the components. There are a few ways to go about this in React:
- Composition - Props which accept React components which also promotes code reuse
- Render props are function props that a component uses to know what to render. It also helps in reusing behavior without bothering about the appearance
styleprops - Allows users to inject class namaes and/or styling attributes to inner DOM elements
With the basics of the component covered, we can dive into specific areas which the component might need special attention to. Note that there almost definitely won't be enough time to cover every area, and not every area will be very relevant to the component at hand.
Showing knowledge about these areas and being able to dive deep into them are traits of senior developers.
User experience (UX)
UX might not fall squarely under engineering but good front end engineers have good understanding of UX and building UI with great UX. There are too many UX practices to be aware of, but the most common ones/low hanging fruits are:
- Reflect state of the component to the user - If there's a pending background request, show a spinner. If there's an error, make sure to display it instead of silently failing
- Display an empty state if there are no items in a list instead of not rendering anything
- Destructive actions should have a confirmation step, especially irreversible ones
- Disable interactive elements if they trigger an async request! Prevents double firing of events
- Handle extreme cases
- Strings can be really long/short and your UI should not look weird in either case. For long strings, they can have their contents truncated and hidden behind a "View more" button
- If there are many items to display within a component, they shouldn't all be displayed on the screen at once and making the page extremely long/wide. Paginate the items or contain them within a container with a maximum width/height
In front end, performance typically refers to a few things - loading speed, how fast the UI responds to user interactions, memory space (heap) required by the component.
- Responsiveness to user interactions
- If a user interaction results in displaying of data that has to be loaded over the network, there will be a delay between the user interaction and updating of the UI. Minimizing that delay or removing it entirely is the key to improving responsiveness.
- Memory space - The more memory your component takes up on the page, the slower the browser performs and the experience will feel sluggish/janky. If your component has to render hundreds/thousands of items (e.g. number of images in a carousel, number of items in a selector), memory space might become significant.
- Render only what is displayed on the screen - For example, in a selector, only a few items are displayed to the user even if the list can contain hundreds of elements. Rendering all of them into the browser would be a waste of processing power and memory space. We can leverage a technique called windowing/virtualization to emulate a list with many elements while only rendering a few as possible to make the final result look as if there was no optimization done (especially preserving scroll height). Read more about virtualization here.
- Lazy loading/load only necessary data - For example, in a photo gallery component, a user can have hundreds and thousands of photos, but it won't be feasible to load all of them eagerly. Most likely the user won't be browsing all of them in that session too. An optimization could be to load only the ones that the user is likely to view, or those that are within the viewport (which we call "above the fold"). The rest of the photos can be loaded on demand, which introduces responsiveness delay, but the next tip will help you to handle that.
- Preloading/prefetching data ahead of time - For example, in an image carousel where there are too many images to load beforehand, an optimization could be to load the next image ahead of time while the user is still on the current image, such that when the user clicks the "Next" button, there's no network delay needed because the next image has already been loaded. This technique can also be modified to load the next N images to handle the case where users click "Next" in rapid succession.
Accessibility (a11y) is the practice of making your websites usable by as many people as possible.
- Color contrasts (e.g. color blindness)
- Keyboard friendliness (e.g. people with limited fine motor control)
- Visual Impairment (e.g. blind)
- Transcripts for audio (e.g. deaf)
Not everyone surfs the web the same way; some people use screenreaders and keyboards exclusively (no mouse)! Here are some basic tips for achieving a11y in UI components:
- Foreground colors should have sufficient contrast from the background colors
- Use the right HTML tags for semanticness, or the right
- Clickable items should have
tabindexattribute (so that they are focusable) and also "cursor: pointer" styling to indicate that they can be clicked on
- Images should have
alttext, which will be read out by screen readers and act as a fallback description if the image fails to load
aria-labels help to provide context to elements which are non-obvious to non-visual users. E.g. an icon button without any text label within it should have an
aria-labelattribute so that the intention is clear for users who can't see the icon
a11y is one of the most commonly neglected areas as most of the time they're invisible to the developer. Showing knowledge of a11y and possessing the skills to create accessible components will definitely reflect well on you. More reading on Web Accessibility.
Internationalization (i18n) is the design and development of a product, application or document content that enables easy localization for target audiences that vary in culture, region, or language. Typically components shouldn't have to worry about i18n unless under few specific circumstances:
- Component uses strings - Strings used in the component shouldn't be hardcoded to a specific language (e.g. "Prev"/"Next" in the controls of a photo gallery component). The strings can be specified as a prop with the English version as default
- Order of content matters - Does your component support RTL (right to left) languages like Arabic and Hebrew?
Is the component expected to be used on mobile web? Mobile devices have unique constraints - they have less powerful hardware and viewport size is smaller. Hence things could be done differently to allow the component to work better on mobile devices - making a conscious effort to:
- Not use too much memory - using too much memory makes the device perform slower
- Increasing the hit box of interactive elements - fingers have an easier time tapping on the right element
Most of the time, components aren't exposed to security vulnerabilities, but it can still happen. Here are the more common security vulnerabilities you should be aware of:
- XSS - Is your component vulnerable to cross-site scripting (XSS)? E.g. Do you render user input via
- CSRF (Cross-Site Request Forgery)