<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: rem</title>
    <description>The latest articles on DEV Community by rem (@remrem).</description>
    <link>https://dev.to/remrem</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F871922%2F5421d28f-b86a-4276-b3c2-9e3568b39403.png</url>
      <title>DEV Community: rem</title>
      <link>https://dev.to/remrem</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/remrem"/>
    <language>en</language>
    <item>
      <title>A guide to frontend migrations</title>
      <dc:creator>rem</dc:creator>
      <pubDate>Sun, 17 Jul 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/remrem/a-guide-to-frontend-migrations-4a0h</link>
      <guid>https://dev.to/remrem/a-guide-to-frontend-migrations-4a0h</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The frontend world innovates fast. In &lt;a href="https://frontendmastery.com/posts/the-new-wave-of-react-state-management/"&gt;the new wave of React state management&lt;/a&gt;, we saw how new approaches to a problem space let us tackle common pain points, in new ways that make our life easier.&lt;/p&gt;

&lt;p&gt;Despite the cycle of new tools and patterns becoming available. Working with "legacy" technologies and patterns is the day-to-day norm as developers. &lt;/p&gt;

&lt;p&gt;The adoption of new tools and coding patterns is gradual for larger projects. &lt;/p&gt;

&lt;p&gt;For projects that have been around a while, it's common to have collected multiple overlapping approaches, and tools, that have been introduced over time.&lt;/p&gt;

&lt;p&gt;When we start collecting more than one tool or pattern to solve the one specific problem, complexity starts to multiply. &lt;/p&gt;

&lt;p&gt;Performance also suffers because we require users to download and run code that solves the same problem multiple times unnecessarily. &lt;/p&gt;

&lt;p&gt;Actively managing these migrations between tools, patterns, and frameworks, is key to maintaining simple and fast frontends at scale. &lt;/p&gt;

&lt;p&gt;So what are the proven best practices for migrating between frontend tools and frameworks?&lt;/p&gt;

&lt;p&gt;This guide will first go over the strategies for tackling frontend framework migrations. We'll then flesh out the principles for managing the adoption of new libaries and tools in our frontends.&lt;/p&gt;

&lt;h2&gt;
  
  
  The types of frontend migrations
&lt;/h2&gt;

&lt;p&gt;If you stay in the frontend world long enough, it's likely at some point you'll work on a migration type project. Let's go over the two most common ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Framework migrations
&lt;/h3&gt;

&lt;p&gt;These are a large, macro style migration and often the most complex. Some of the ways one may encounter these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Through acquisitions&lt;/strong&gt; where the company that got acquired has a different legacy frontend tech stack, and we now need to integrate it in our existing frontend. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A legacy application needs to be modernized&lt;/strong&gt;. Sometimes a legacy application that hasn't been touched in years, suddenly needs a set of new features. That now makes sense to build in the new stack. Or the experience needs to align with existing products.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling issues&lt;/strong&gt; can crop up. Organizational ones, like hiring developers. It also includes performance and reliability issues. This occurs when working on a legacy code base that has turned into spaghetti, that relies on a few people who understand it. We'll touch on the topic of rewrites in a bit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Library and tool migrations
&lt;/h3&gt;

&lt;p&gt;These come when we introduce a new tool or pattern that makes our job easier in some way. For smaller projects, quick migrations tend to be easily manageable.&lt;/p&gt;

&lt;p&gt;For larger projects, we need strategies. Because the adoption often has second order knock on effects, like introducing new concepts, coding patterns, or what folder structures now make sense with the new approach.&lt;/p&gt;

&lt;p&gt;Here are some common examples of when adoption makes sense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solves the problem in a simpler way&lt;/strong&gt;. Simple here means it solves the specific problem without any extraneous kilobytes or concepts. A lot of times we adopt tools that are much more powerful than what the problem demands. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solves the problem more performantly&lt;/strong&gt;. One example here is date formatting. A common frontend problem. Solved with &lt;code&gt;moment&lt;/code&gt; at &lt;code&gt;290kb&lt;/code&gt; and &lt;code&gt;date-fns&lt;/code&gt; at &lt;code&gt;89kb&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Has wider community support&lt;/strong&gt;. Sometimes the new approach may not necessarily solve it better than the previous one. But has a wider community around it. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Making it easier to hire, find answers to common issues, and receive future upgrades. A common example is &lt;a href="https://stripe.com/blog/migrating-to-typescript"&gt;migrating from Flow to Typescript&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration strategies: gradual vs big bang
&lt;/h2&gt;

&lt;p&gt;The analogy of waterfall versus agile is a good framing to understand the trade-offs between big bang migrations and incremental ones.&lt;/p&gt;

&lt;p&gt;Waterfall methods can work well for projects that are relatively predictable (like the Flow to Typescript example above).&lt;/p&gt;

&lt;p&gt;Big bang approaches batch up the changes, and update things in a single fell swoop. This means the change is "atomic" in the sense that the codebase avoids having a prolonged transitional state. &lt;/p&gt;

&lt;p&gt;This is risky though, because we can end up &lt;a href="https://blog.codinghorror.com/dont-go-dark/"&gt;going dark&lt;/a&gt; for lengthy periods of time. It assumes things won't change during the time we spend migrating in the dark, before pushing it out.&lt;/p&gt;

&lt;p&gt;In contrast, an agile approach deals with unknowns through fast feedback loops and smaller iterations, that allow us to learn as we go. This is also the benefit of going gradual.&lt;/p&gt;

&lt;p&gt;The risk here is staying in a transitional period indefinitely. Where we need to manage the split brain between the two different approaches. A major source of complexity.&lt;/p&gt;

&lt;p&gt;In practice different priorities often come up, which can pause migration efforts. &lt;br&gt;
Over time this leads to stale code no on wants to touch, that becomes risky to change.  &lt;/p&gt;

&lt;p&gt;A gradual approach is effective, but also has strong negatives if not managed proactively.&lt;/p&gt;

&lt;p&gt;For very large code bases under active feature development, where stopping the world for a migration is not feasible. Defaulting to a gradual migration will often be the pragmatic choice. &lt;/p&gt;
&lt;h2&gt;
  
  
  Frontend framework migrations
&lt;/h2&gt;

&lt;p&gt;Sometimes we may find ourselves needing to migrate to an entirely different framework. Or integrate a legacy framework into our existing one. The example we'll use is migrating a Backbone SPA to an existing tech stack in React.&lt;/p&gt;
&lt;h3&gt;
  
  
  Should you rewrite?
&lt;/h3&gt;

&lt;p&gt;There’s lots of interesting debate around this. Some famously taking the position that you &lt;a href="https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/"&gt;should never rewrite your software&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Where others disagree pointing out that &lt;a href="https://businessofsoftware.org/2015/10/david-heinemeier-hansson-rewrite-basecamp-business-of-software-conference-video-dhh-bos2015/"&gt;sometimes a rewriting from scratch is good&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We won't go into the debate here. But here are some common pitfalls to be aware of when migrating to a new frontend framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not considering potential trade-offs&lt;/strong&gt;. We might adopt a new framework thinking all our problems will be solved. Unfortunately there's no guarantee the second time will be better than the first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is because first draft of anything is usually bad. Often times it takes a while to find the patterns for your app when adopting a new framework. &lt;/p&gt;

&lt;p&gt;And these may change during the migration process (think class based components to functions with hooks). Leaving you in multiple states of transition at once.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Under-estimating the challenge&lt;/strong&gt;. A big part of underestimating the challenge is knowing that it's been done before. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Legacy code has lots of small bug fixes that developers have accumulated over the years. Maybe without tests, comments, or any context. &lt;/p&gt;

&lt;p&gt;That tribal knowledge gets lost to the sands of time. It's easy to miss the edge cases when starting in anew without understanding how the old way works. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Making updates to the UX at the same time&lt;/strong&gt;. Scope creep is common in these migrations. This contributes to prolonged timelines because we end up replacing the engine while the plane is still flying, and also building new features at the same time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Not fully committing&lt;/strong&gt;. Large frontend migrations take time. Sometimes they are multi-year projects. It's difficult when momentum is lost, and other priorities take place. As long as we have at least one user on the old stack, it means we can't delete the old code, and need to maintain a split brain between the old and new. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Progressive strategies for migrating frameworks
&lt;/h3&gt;

&lt;p&gt;Let's take a look at two common progressive migration strategies:&lt;/p&gt;
&lt;h4&gt;
  
  
  Migrating "outside in"
&lt;/h4&gt;

&lt;p&gt;This is a top down approach. We can think about this as an adaption of the &lt;a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/strangler-fig"&gt;strangler pattern&lt;/a&gt;, often used with backend service migrations.&lt;/p&gt;

&lt;p&gt;The idea here is that we first migrate the application's shell to the new stack. And migrate the legacy pages one by one at the route level.&lt;/p&gt;

&lt;p&gt;The "shell" in this example is referring to the container that handles the rendering of pages. &lt;/p&gt;

&lt;p&gt;Which means the router, and top level page layout components. It can also extend to include the shared data layer (if applicable) and things like analytics and monitoring etc. &lt;/p&gt;

&lt;p&gt;Let's take an example migrating our legacy Backbone SPA to React.&lt;/p&gt;

&lt;p&gt;Assuming we have our new (or existing) React application set up. We now need to create an abstraction that allows us to render Backbone pages as React components.&lt;/p&gt;

&lt;p&gt;Here's some pseudoish code on what this might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LegacyBackbonePage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;pageKey&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// run any effects that may be needed&lt;/span&gt;
    &lt;span class="c1"&gt;// e.g subscribe to events the legacy can send to the React world&lt;/span&gt;
    &lt;span class="c1"&gt;// ..&lt;/span&gt;
    &lt;span class="c1"&gt;// can also render any common infrastructure components and set up analytics etc&lt;/span&gt;
    &lt;span class="c1"&gt;// ..&lt;/span&gt;
    &lt;span class="c1"&gt;// render element container the Backbone SPA takes over when rendered on the page&lt;/span&gt;
    &lt;span class="c1"&gt;// in the legacy Backbone app this is usually the &amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;legacy-backbone-root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the outside looking in this is just a React component. The component does some set-up and renders an element container for the Backbone page to render into. &lt;/p&gt;

&lt;p&gt;To keep everything together, it's often easier to copy the legacy code into a directory in the same repo as the React repo.&lt;/p&gt;

&lt;p&gt;Getting set up is the easy part. If you're lucky things may "just work" depending on how the legacy pages work. But probably not. &lt;/p&gt;

&lt;p&gt;The hard part of legacy migrations like this, is once the legacy code has taken over that element and rendered itself, there's usually a series of weird bugs to address one by one. &lt;/p&gt;

&lt;p&gt;A common challenge is intermediating the client side routers if they conflict. In addition to managing any global styles or scripts that have side effects, that run as a result of loading the legacy code into the React SPA.&lt;/p&gt;

&lt;p&gt;We'll hand-wave over these hairy bits for now. &lt;/p&gt;

&lt;p&gt;This approach allows you to start getting the benefits of the new stack once the shell has been migrated. And creates clear boundaries between legacy and new. &lt;/p&gt;

&lt;p&gt;It allows you to release newly migrated pages progressively at the route level which can be controlled via feature flags.&lt;/p&gt;

&lt;h4&gt;
  
  
  Migrating "inside out"
&lt;/h4&gt;

&lt;p&gt;This is a bottom up approach. Where the apps shell and entry point stays the same, where legacy pieces are replaced piecemeal from the "inside out". &lt;/p&gt;

&lt;p&gt;Going back to our Backbone to React example. This would involve creating a way for Backbone code to create &lt;a href="https://reactjs.org/docs/react-dom-client.html#createroot"&gt;new react roots&lt;/a&gt; and render them into sections on the page. That React can then take over.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .. other imports&lt;/span&gt;
&lt;span class="c1"&gt;// import our React component into the Backbone world&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ComponentWrittenInReact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/path/to/cool-new-component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="c1"&gt;// a bunch of legacy Backbone app code &lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="c1"&gt;// render the new component into an element container&lt;/span&gt;
&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// no JSX so we use the imperative API&lt;/span&gt;
  &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;ComponentWrittenInReact&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// component props and handler functions passed from legacy&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;someData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;someCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;containerElement&lt;/span&gt; &lt;span class="c1"&gt;// the root HTML element React will render into and take over&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we pass the initial data and function references to methods the React component can invoke. &lt;/p&gt;

&lt;p&gt;In practice orchestrating communication between components in the legacy world and the new world is a challenge. &lt;/p&gt;

&lt;p&gt;One simple approach is using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent"&gt;custom events&lt;/a&gt;, which keeps things decoupled. But doesn't scale that well, and can be a pain to debug.&lt;/p&gt;

&lt;h4&gt;
  
  
  What approach is best to take?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Outside in&lt;/strong&gt; is the often better approach if we know for certain we want to migrate to the new framework. It requires us to bite the bullet upfront, and set up the initial shell and infrastructure. Which may not always be feasible. &lt;/p&gt;

&lt;p&gt;However this pays off once done, because you can reap the benefits of the new framework early. Which builds strong momentum. And you have clear boundaries at the route level between what is old and what is new.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inside out&lt;/strong&gt; is good for exploring whether or not you want to commit to a full migration. It's easier to get started with because you don’t have to touch any of the surrounding shell or infrastructure like routing. It allows replacing sections piecemeal to experiment. In addition to going route by route.&lt;/p&gt;

&lt;p&gt;You can still switch to a top-down, outside-in model once you decide that the framework is worth investing in further. &lt;/p&gt;

&lt;h4&gt;
  
  
  Testing and releasing incrementally
&lt;/h4&gt;

&lt;p&gt;In either case, testing can be tricky. It's often effective to rely on end to end tests to ensure the pages or components load correctly, and are interactable on a real instance.&lt;/p&gt;

&lt;p&gt;Using a &lt;a href="https://launchdarkly.com/features/feature-flags/"&gt;feature flag tool&lt;/a&gt; also allows us to gate newly migrated pieces that can be turned on and off during runtime. This allows for progressively releasing migrated pieces to avoid going dark for long periods of time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend library and tool migration strategies
&lt;/h2&gt;

&lt;p&gt;We introduce a new tool or pattern that will make our lives easier in the long term. The cost of this in the short to medium term (and often times indefinitely) is the transitional period.&lt;/p&gt;

&lt;p&gt;Some common pitfalls in large projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Old patterns keep replicating&lt;/strong&gt;. Especially as a new starters, we often look around to see how other people did something and take inspiration from those approaches. This effect often tends to multiply quickly when deprecated patterns are replicated. Extending the transition and making it harder to switch to newer approaches.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance degradation&lt;/strong&gt;. As different tools and approaches accumulate for the same problem, we end up shipping that code down to users. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a somewhat contrived extreme example. We can image an app that started with &lt;code&gt;Redux&lt;/code&gt;, later adopted &lt;code&gt;react-query&lt;/code&gt; with another other team using &lt;code&gt;apollo-client&lt;/code&gt; somewhere else. &lt;/p&gt;

&lt;p&gt;Multiply this for each problem domain and throw a framework migration in the mix, and that's how some web apps end up sending megabytes of code down the wire. &lt;/p&gt;

&lt;p&gt;This phenomenon of so much Javascript is a main driver for the trend of pushing more back to the server, and only sending what is absolutely necessary. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decreased velocity&lt;/strong&gt;. When these transitional states linger, it creates stale parts of the code. This creates a reluctance to touch old portions of the code base with the increased risk of causing issues. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also becomes harder to onboard new developer's, as tribal knowledge usually becomes necessary to understand the older parts that become untouched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strategies for mitigating these pain points
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Break transitions into distinct phases&lt;/strong&gt;. Often times a migration will need several intermediary stages. Knowing what stage you are in, and what comes next can help you track progress and maintain momentum with clear goals.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Get buy in on the migration plan&lt;/strong&gt;. In &lt;a href="https://frontendmastery.com/posts/the-three-ds-of-frontend-feature-leading/"&gt;the 3'ds of frontend feature leading&lt;/a&gt; we highlighted the fact that frontend software engineering is a team sport, and building consensus on what's important is a huge part of that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Track progress&lt;/strong&gt; through charts showing the percentage of remaining code required migrate can help maintain momentum. Having this clearly laid out helps assign owners who can be accountable for driving the work to eventual completion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Investing in static code analysis&lt;/strong&gt; helps when leveraging automated change tools like &lt;a href="https://www.sitepoint.com/getting-started-with-codemods/"&gt;code mods&lt;/a&gt;. Writing code that is statically analyzable is a key part of this. This means being consistent across the board. Which helps us find and update common patterns in bulk. Some strategies to help with this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid import aliasing - renaming imports like &lt;code&gt;{ Button as RenamedButton }&lt;/code&gt; can make things hard to find at consumption points because you lose the context.&lt;/li&gt;
&lt;li&gt;Dynamic behaviour such as spreading props into components can be hard to search for usages, and statically analyze.&lt;/li&gt;
&lt;li&gt;Rename with overly explicit names for things that deprecated and shouldn't be used anymore. &lt;a href="https://github.com/facebook/react/blob/b2ca3349c27b57b1e9462944cbe4aaaf76783d2b/src/React.js#L67"&gt;See this variable name&lt;/a&gt; in the React code base as an example. Can also apply to package names and directories.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lean on automation&lt;/strong&gt;. There are regex based tools you can leverage that detect deprecated usages and automatically leave comments on pull requests.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can have bots automatically comment to use the new pattern in instead. It helps if you have documentation around your choices that you can point to.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom ES lint rules&lt;/strong&gt; Following the point above, you can write &lt;a href="https://developers.mews.com/how-to-write-custom-eslint-rules/"&gt;custom es-lint rules&lt;/a&gt; to help enforce the migration approach you are committed to.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Documentation&lt;/strong&gt; if it actually exists, and gets updated, is very useful. Especially for new starters who join who can read up and get up to speed with the specific tools, technologies and patterns that are used.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;For large frontend code bases that have been around a while, it's easy to accumulate multiple transitions between different tools, coding patterns and even frameworks. &lt;/p&gt;

&lt;p&gt;This is an inevitable part of starting with one approach and over time introducing newer ways that attempt to make our lives easier in the longer term.&lt;/p&gt;

&lt;p&gt;For large frontend code bases, managing these migrations is a key part in taming the complexity of the frontend architecture and avoiding performance nightmares.&lt;/p&gt;

&lt;p&gt;Today's cool approach is tomorrow's legacy. Whenever introducing a new pattern or tool, it's useful to include a plan for both migrating to and also away from the previous way.&lt;/p&gt;

&lt;p&gt;For larger framework migrations - it's often better to go top down, "outside in" if you know for certain of the framework you want to commit to.&lt;/p&gt;

&lt;p&gt;A bottom-up "inside out" approach works better for exploration on whether or not you want to commit to a migration by replacing smaller sections of your app with the new framework.&lt;/p&gt;

&lt;p&gt;There's a lot more to say on this topic than has been covered in this guide. One aspect of &lt;a href="https://frontendmastery.com/posts/understanding-micro-frontends"&gt;the micro frontend tend&lt;/a&gt; can potentially be useful for handling these types of larger framework migrations at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=d0pOgY8__JM"&gt;Evolving complex systems incrementally at Facebook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.atlassian.com/engineering/software-engineering-principles-massive-projects"&gt;Software engineering principles for massive migration projects&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/"&gt;Things you should never do&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://stripe.com/blog/migrating-to-typescript"&gt;Migrating millions of lines of code to Typescript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/airtable-eng/the-continual-evolution-of-airtables-codebase-migrating-a-million-lines-of-code-to-typescript-612c008baf5c"&gt;The continual evolution of Airtable’s codebase&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Understanding micro frontends</title>
      <dc:creator>rem</dc:creator>
      <pubDate>Sat, 16 Jul 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/remrem/understanding-micro-frontends-3ago</link>
      <guid>https://dev.to/remrem/understanding-micro-frontends-3ago</guid>
      <description>&lt;h2&gt;
  
  
  What are micro frontends?
&lt;/h2&gt;

&lt;p&gt;Micro frontends are a set of principles for structuring large frontend applications. Originally inspired by the backend micro-services trend. &lt;/p&gt;

&lt;p&gt;While micro frontends are a trending topic of interest, there's something about them that's hard to pin down precisely. When understanding a new trend or technology, it's often useful to start understanding the problem space first. &lt;/p&gt;

&lt;p&gt;There are many resources online that go over the potential benefits of micro frontends that we won't cover here. Instead we'll start by understanding the problems they aim to solve. And the trade-offs they make in the pursuit of solving those problems. &lt;/p&gt;

&lt;h2&gt;
  
  
  What problems do micro frontends try to solve?
&lt;/h2&gt;

&lt;p&gt;The simplest way to understand micro frontends is seeing them as a technical solution to organizational problems that occur at scale.&lt;/p&gt;

&lt;p&gt;For large organizations, with many frontend teams wanting to ship things fast, we'd want those teams to be decoupled. So they can work independently without getting in each others way. &lt;/p&gt;

&lt;p&gt;What this means in practice on the frontend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Independently buildable frontend assets&lt;/strong&gt; allows teams to own their own builds. This avoids lengthy build times, and prevents individual teams breaking the build for the rest of the organization. Blocking others while it gets fixed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Independently deployable frontend assets&lt;/strong&gt; allows decoupled these teams to ship their code to production regularly, with without needing to have complex deployment sequences. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This also means when bugs crop up, and incidents occur, things can be rolled back automatically, without disrupting other teams and having their code rolled back as well. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clear areas of ownership&lt;/strong&gt; is necessary at scale. In large organizations if no one owns something, it's almost like it doesn't exist. Because no one is accountable for keeping it up to date, it often becomes stale and hard to change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are some of the core problems micro frontends hope to address. They are legitimate problems, quite difficult to solve. &lt;/p&gt;

&lt;p&gt;If solved correctly, it can really accelerate the development speed of teams. This might explain the hype around the potential of the micro frontend trend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining micro frontends
&lt;/h2&gt;

&lt;p&gt;Just as "micro services" is a kind of guiding principle in the backend world, on  how to structure services, micro frontends can be seen in the same way. &lt;/p&gt;

&lt;p&gt;As a set of guiding principles enabled by specific technologies.&lt;/p&gt;

&lt;p&gt;There are underlying technologies like &lt;a href="https://webpack.js.org/concepts/module-federation/"&gt;webpack module federation&lt;/a&gt; that make a micro frontend approach possible. And popular frameworks like &lt;a href="https://single-spa.js.org/"&gt;single SPA&lt;/a&gt; that provide batteries included set up. &lt;/p&gt;

&lt;h3&gt;
  
  
  The following principles of micro frontends taken from &lt;a href="https://micro-frontends.org/"&gt;micro-frontends.org&lt;/a&gt; are paraphrased here:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Be technology agnostic&lt;/strong&gt; means teams can chose whatever frontend tech stack they want. The recommendation seems standardize on &lt;a href="https://web.dev/custom-elements-v1/"&gt;web components&lt;/a&gt; as common shared primitives.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Isolate team code&lt;/strong&gt; means focussing on decoupling teams through independently built, self contained apps that are deployed separately. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Establish team prefixes&lt;/strong&gt; when you have to share stuff. This means a name-spacing for custom event names and things like locale storage and cookies to avoid collisions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Favor native browser features over custom APIs.&lt;/strong&gt;&lt;br&gt;
This often means using custom elements and web components and custom events for inter micro-frontend communication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build a resilient site&lt;/strong&gt; means build your micro frontends with progressive enhancement in mind.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these are somewhat open to interpretation. Which is probably by design, as micro frontends don't point to a specific universal implementation. That is partly one of the reasons why there's confusion around what micro frontends are used for.&lt;/p&gt;

&lt;h2&gt;
  
  
  A comparison with the islands architecture
&lt;/h2&gt;

&lt;p&gt;Web frameworks like &lt;a href="https://fresh.deno.dev/"&gt;Fresh&lt;/a&gt;, &lt;a href="https://astro.build/"&gt;Astro&lt;/a&gt; and &lt;a href="https://markojs.com/"&gt;Marko&lt;/a&gt; promote the &lt;a href="https://jasonformat.com/islands-architecture/"&gt;"component islands pattern"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Micro frontends on the surface look similar, due to rendering a page build from independent sections of components. &lt;/p&gt;

&lt;p&gt;But the underlying philosophies are quite different. &lt;/p&gt;

&lt;p&gt;The guiding principle for the island architecture pattern is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To default to server side rendering over client side rendering&lt;/li&gt;
&lt;li&gt;Only send what is absolutely necessary to the client for interactivity. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The focus is on minimizing any unnecessary overhead of Javascript.&lt;/p&gt;

&lt;p&gt;It's a almost a throwback to traditional multi page architectures, where Javascript was "sprinkled" on on top of server rendered HTML pages to give them a bit interactivity. &lt;/p&gt;

&lt;p&gt;In this cycle of innovation, the "sprinkles" are instead component based islands authored using modern technologies like &lt;a href="https://preactjs.com/"&gt;Preact&lt;/a&gt; rather than something like &lt;code&gt;jQuery&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Micro frontends, on the other hand, are all about solving organizational issues, rather than performance ones. And are used in the context of large SPAs worked on by many teams. &lt;/p&gt;

&lt;h2&gt;
  
  
  Potential issues with micro frontends
&lt;/h2&gt;

&lt;p&gt;Micro frontends have noble goals, and aim to solve real organizational issues. &lt;/p&gt;

&lt;p&gt;However based on their philosophy outlined above, we can see some aspects that would need strict governance in practice in order to not fall into the falling pitfalls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Potential performance pitfalls&lt;/strong&gt;. At large scale, giving teams the freedom to chose whatever tech stack they want on the frontend has some significant trade-offs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the context of micro-services on the the backend. It makes sense for teams to chose their stack. Because the end user doesn't need to download and run that code.&lt;/p&gt;

&lt;p&gt;On the frontend though, things are different. You end up forcing users to download and run code that solves all sorts of common problems like styling, fetching and mutating data, date formatting etc. Many times over, in different ways, with different tools and frameworks. That's a lot of overhead. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can't we just code split everything?&lt;/strong&gt; Each micro frontend can be selectively code-split on a page load. Code-splitting isn't a silver bullet. And it's often the network waterfall of components loading, then fetching data, which results in more async loading, which is the real culprit of slow page loads. Especially so on mobile, that often has more sensitivity to network latencies.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Difficult dependency management&lt;/strong&gt;. Even if the same library is shared to solve a particular problem, at scale there is always the problem of different versions of that library needing to be kept up to date. Especially for things like high priority security updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multiple duplicated transitive dependencies lead to users re-downloading the same code (in different versions) multiple times. This is not a trivial problem to manage with lots of teams, even when using a single bundler, let alone one for each independent team.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inconsistent look and feel&lt;/strong&gt;. Decoupled teams are often necessary at scale. However often times with completely isolated teams, it's easy to reinvent the wheel many times over. Leading the overall whole of the application feeling disjointed and janky.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requires strong governance, oversight and guidelines&lt;/strong&gt;. Similar to backend micro services. A key question here, is how micro should a micro frontend be? &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we break things down too much we run the risk of creating dependencies that require us to deploy multiple frontends together. Which invalidates a major problem we originally set out to solve.&lt;/p&gt;

&lt;p&gt;These points are not to say micro frontends don't have their use cases. But their adoption should take into consideration mitigation strategies for these &lt;a href="https://en.wikipedia.org/wiki/Murphy%27s_law"&gt;potential things to go wrong&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A use-case for frontend framework migrations
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://frontendmastery.com/posts/frontend-migration-guide"&gt;a guide to managing frontend migrations&lt;/a&gt; we looked at the "outside in" approach to managing frontend framework migrations.&lt;/p&gt;

&lt;p&gt;There is a good potential use case for micro frontends here when you have no choice but to integrate two different frontend tech stacks and adopt a transitional architecture. &lt;/p&gt;

&lt;p&gt;Using a framework like &lt;a href="https://single-spa.js.org/"&gt;single SPA&lt;/a&gt; as the application shell to manage the transitional architecture. &lt;/p&gt;

&lt;p&gt;That said, there are often simpler approaches that don't require the adoption of a framework that can work as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;We can understand the rise of micro frontends by looking at the organizational issues they aim to solve.&lt;/p&gt;

&lt;p&gt;These are real issues, that if solved correctly, can have a great benefit to frontend teams. &lt;/p&gt;

&lt;p&gt;If you don't have these organizational problems, then you probably don't need, or want, to adopt a micro frontend architecture.&lt;/p&gt;

&lt;p&gt;If you do have these problems, micro frontends are worth checking out. But as we've seen they are not without significant trade-offs, often at the expense of the end user, that need to be actively managed.&lt;/p&gt;

&lt;p&gt;It's good to keep in mind micro frontends are not the only way to solve those organizational issues, but that's beyond the scope of this article for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.patterns.dev/posts/islands-architecture/"&gt;The islands architecture pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://netflixtechblog.com/how-we-build-micro-frontends-with-lattice-22b8635f77ea"&gt;Netflix - how we build miro frontends with lattice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webpack.js.org/concepts/module-federation/"&gt;Webpack module federation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.thoughtworks.com/radar/techniques/micro-frontends"&gt;Thoughtworks technology radar - micro frontends&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.atlassian.com/engineering/performance-in-jira-front-end-solving-bundle-duplicates-with-webpack-and-yarn"&gt;Managing frontend dependencies at scale&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The new wave of React state management</title>
      <dc:creator>rem</dc:creator>
      <pubDate>Thu, 30 Jun 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/remrem/the-new-wave-of-react-state-management-4c8o</link>
      <guid>https://dev.to/remrem/the-new-wave-of-react-state-management-4c8o</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As React applications grow in size and complexity, managing shared global state is a challenge. The general advice is to only reach for global state management solutions when you need it. &lt;/p&gt;

&lt;p&gt;This post will flesh out the core problems global state management libraries need to solve. &lt;/p&gt;

&lt;p&gt;Understanding the underlying problems will help us assess the trade-offs made the "new wave" of state management approaches. For everything else, it's often better to start local and scale up only as needed.&lt;/p&gt;

&lt;p&gt;React itself does not provide any strong guidelines for how to solve this for shared global application state. As such the React ecosystem has collected numerous approaches and libraries to solve this problem over time. &lt;/p&gt;

&lt;p&gt;This can make it confusing when assessing which library or pattern to adopt. &lt;/p&gt;

&lt;p&gt;The common approach is to outsource this and use whatever is most popular. Which as we'll see was the case with the widespread adoption of Redux early on, with many applications not needing it. &lt;/p&gt;

&lt;p&gt;By understanding the problem space state management libraries operate in, it allows us to better understand why there are so many different libraries taking different approaches. &lt;/p&gt;

&lt;p&gt;Each makes different tradeoffs against different problems, leading to numerous variations in API's, patterns and conceptual models on how to think about state.&lt;/p&gt;

&lt;p&gt;We'll take a look at modern approaches and patterns that can be found in libraries like &lt;a href="https://recoiljs.org/"&gt;Recoil&lt;/a&gt;, &lt;a href="https://jotai.org/"&gt;Jotai&lt;/a&gt;, &lt;a href="https://github.com/pmndrs/zustand"&gt;Zustand&lt;/a&gt;, &lt;a href="https://github.com/pmndrs/valtio"&gt;Valtio&lt;/a&gt; and how others like &lt;a href="https://github.com/dai-shi/react-tracked"&gt;React tracked&lt;/a&gt; and &lt;a href="https://react-query.tanstack.com/overview"&gt;React query&lt;/a&gt; and how fit into the ever evolving landscape. &lt;/p&gt;

&lt;p&gt;By the end we should be more equipped to accurately assess the trade-offs libraries make when we need to chose one that makes sense for our applications needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problems global state management libraries need to solve
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ability to read stored state from anywhere in the component tree.&lt;/strong&gt; This is the most basic function of a state management library. &lt;/p&gt;

&lt;p&gt;It allows developers to persist their state in memory, and avoid the issues prop drilling has at scale. Early on in the React ecosystem we often reached for Redux unnecessarily to solve this pain point.&lt;/p&gt;

&lt;p&gt;In practice there are two main approaches when it comes to actually storing the state. &lt;/p&gt;

&lt;p&gt;The first is inside the React runtime. This often means leveraging API's React provides like &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useRef&lt;/code&gt; or &lt;code&gt;useReducer&lt;/code&gt; combined with React context to propagate a shared value around. The main challenge here is optimizing re-renders correctly.&lt;/p&gt;

&lt;p&gt;The second is outside of React's knowledge, in module state. Module state allows for singleton-like state to be stored. It's often easier to optimize re-renders through subscriptions that opt-in to re-rendering when the state changes. However because it's a single value in memory, you can't have different states for different subtrees.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ability to write to stored state.&lt;/strong&gt; A library should to provide an intuitive API for both reading and writing data to the store. &lt;/p&gt;

&lt;p&gt;An intuitive API is often one that fits ones existing mental models. So this can be somewhat subjective depending on who the consumer of the library is.&lt;/p&gt;

&lt;p&gt;Often times clashes in mental models can cause friction in adoption or increase a learning curve. &lt;br&gt;
A common clashing of mental models in React is mutable versus immutable state.&lt;/p&gt;

&lt;p&gt;React's model of UI as a function of state lends itself to concepts that rely on referential equality and immutable updates to detect when things changes so it can re-render correctly. But Javascript is a mutable language.&lt;/p&gt;

&lt;p&gt;When using React we have to keep things like reference equality in mind. This can be a source of confusion for Javascript developers not used to functional concepts and forms part of the learning curve when using React.&lt;/p&gt;

&lt;p&gt;Redux follows this model and requires all state updates be done that in an immutable way. There are trade-offs with choices like this, in this case a common gripe is the amount of boilerplate you have to write to make updates for those used to mutable style updates.&lt;/p&gt;

&lt;p&gt;That's is why libraries like &lt;a href="https://github.com/immerjs/immer"&gt;Immer&lt;/a&gt; are popular that allow developers to write mutable style code (even if under the hood updates are immutable).&lt;/p&gt;

&lt;p&gt;There are other libraries in the new wave of "post-redux" global state management solutions such as &lt;a href="https://valtio.pmnd.rs/"&gt;Valtio&lt;/a&gt; that allow developers to use a mutable style API.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Provide mechanisms to optimize rendering.&lt;/strong&gt; The model of UI as a function of state is both incredibly simple and productive. &lt;/p&gt;

&lt;p&gt;However the process of reconciliation when that state changes is expensive at scale. And often leads to poor runtime performance for large apps.&lt;/p&gt;

&lt;p&gt;With this model, a global state management library needs to both detect when to re-render when it's state gets updated, and only re-render what is necessary.&lt;/p&gt;

&lt;p&gt;Optimizing this process is one of the biggest challenges a state management library needs to solve.&lt;/p&gt;

&lt;p&gt;There are two main approaches often taken. The first is allowing consumers to manually optimize this process. &lt;/p&gt;

&lt;p&gt;An example of a manual optimization would be subscribing to a piece of stored state through a selector function. Components that read state through a selector will only re-render when that specific piece of state updates.&lt;/p&gt;

&lt;p&gt;The second is handling this automatically for consumers so they don't have to think about manual optimizations. &lt;/p&gt;

&lt;p&gt;Valtio is another example library that use &lt;code&gt;Proxy&lt;/code&gt;'s under the hood to automatically track when things get updated and automatically manage when a component should re-render.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Provide mechanisms to optimize memory usage.&lt;/strong&gt; For very large frontend applications, not managing memory properly can silently lead to issues at scale. &lt;/p&gt;

&lt;p&gt;Especially if you have customers that access these large applications from lower spec devices. &lt;/p&gt;

&lt;p&gt;Hooking into the React lifecycle to store state means it's easier to take advantage of automatic garbage collection when the component unmounts. &lt;/p&gt;

&lt;p&gt;For libraries like Redux that promote the pattern of a single global store, you will need manage this yourself. As it'll continue to hold a reference to your data so that it won't automatically get garbage collected.&lt;/p&gt;

&lt;p&gt;Similarly, using a state management library that stores state outside the React runtime in module state means it's not tied to any specific components and may need to be managed manually.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;More problems to solve:&lt;/strong&gt;&lt;br&gt;
In addition to the foundational problems above, there are some other common problems to consider when integrating with React:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Compatibility with concurrent mode.&lt;/strong&gt; &lt;a href="https://17.reactjs.org/docs/concurrent-mode-intro.html"&gt;Concurrent mode&lt;/a&gt; allows React to "pause" and switch priorities within a render pass. Previously this process was completely synchronous. &lt;/p&gt;

&lt;p&gt;Introducing concurrency to anything usually introduces edge cases. For state management libraries there is the potential for two components to read different values from an external store, if the value read is changed during that render pass. &lt;/p&gt;

&lt;p&gt;This is known as "tearing". This problem lead to the React team creating the &lt;a href="https://reactjs.org/docs/hooks-reference.html#usesyncexternalstore"&gt;useSyncExternalStore&lt;/a&gt; hook for library creators to solve this problem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Serialization of data.&lt;/strong&gt; It can be useful to have fully serializable state so you can save and restore application state from storage somewhere. Some libraries handle this for you while others may require additional effort on the consumers side to enable this. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The context loss problem.&lt;/strong&gt; This is a problem for applications that &lt;a href="https://github.com/facebook/react/issues/13332"&gt;mix multiple react-renderers together&lt;/a&gt;. For example you may have an application that utilizes both &lt;code&gt;react-dom&lt;/code&gt; and a library like &lt;code&gt;react-three-fiber&lt;/code&gt;. Where React can't reconcile the two separate contexts. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The stale props problem.&lt;/strong&gt; Hooks solved a lot of issues with traditional class components. The trade off for this was a new set of problems that come with embracing closures. &lt;/p&gt;

&lt;p&gt;One common issue is data inside a closure no longer being "fresh" in the current render cycle. Leading to the data that is rendered out to the screen not being the latest value. This can be a problem when using selector functions that rely on props to calculate the state. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The zombie child problem.&lt;/strong&gt; This refers to an old issue with Redux where child components that mount themselves first and connect to the store before the parent can cause inconsistencies if that state is updated before the parent component mounts. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A brief history of the state management ecosystem
&lt;/h2&gt;

&lt;p&gt;As we've seen there's a lot of problems and edge cases global state management libraries need to take into account.&lt;/p&gt;

&lt;p&gt;To better understand all the modern approaches to React state management. We can take a trip down memory lane to see the how the pain-points of the past have lead to lessons that we call "best practices" today. &lt;/p&gt;

&lt;p&gt;Often times these best practices are discovered through trial and error and from find that certain solutions don't end up scaling well.&lt;/p&gt;

&lt;p&gt;From the beginning, React's original tagline when it was first released was the "view" in Model View Controller. &lt;/p&gt;

&lt;p&gt;It came without opinions on how to structure or manage state. This meant developers were sort of on their own when it came to dealing with the most complicated part of developing frontend applications.&lt;/p&gt;

&lt;p&gt;Internally at Facebook a pattern was used called &lt;a href="https://facebook.github.io/flux/"&gt;"Flux"&lt;/a&gt;, that lent itself to uni-directional data flow and predictable updates that aligned with React's model of "always re-render" the world. &lt;/p&gt;

&lt;p&gt;This pattern fitted React's mental model nicely, and caught on early in the React ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  The original rise of Redux
&lt;/h3&gt;

&lt;p&gt;Redux was one of the first implementations of the Flux pattern that got widespread adoption. &lt;/p&gt;

&lt;p&gt;It promoted the use of a single store, partly inspired by the Elm architecture, as opposed to many stores that was common with other Flux implementations.&lt;/p&gt;

&lt;p&gt;You wouldn't get fired for choosing Redux as your state management library of choice when spinning up a new project. It also had cool demoable features like ease of implementing undo / redo functionality and time travel debugging. &lt;/p&gt;

&lt;p&gt;The overall model was, and still is, simple and elegant. Especially compared to the previous generation of MVC style frameworks like Backbone (at scale) that had preceded the React model. &lt;/p&gt;

&lt;p&gt;While Redux is still a great state management library that has real use cases for specific apps. Over time there were a few common gripes with Redux that surfaced that lead it to fall out of favour as we learnt more as a community:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Issues in smaller apps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a lot of applications early on it solved the first problem. Accessing stored state from anywhere in the tree to avoid the pains of prop-drilling both data and functions to update that data down multiple levels. &lt;/p&gt;

&lt;p&gt;It was often overkill for simple applications that fetched a few endpoints and had little interactivity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Issues in larger apps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Over time our smaller applications grew into larger ones. And as we discovered that in practice there are many different types of state in a frontend application. Each with their own set of sub-problems.&lt;/p&gt;

&lt;p&gt;We can count local UI state, remote server cache state, url state, and global shared state, and probably more distinct types of state.&lt;/p&gt;

&lt;p&gt;For example with local UI state, prop drilling both data and methods to update that data often becomes a probably relatively quickly as things grow. To solve this, using &lt;a href="https://frontendmastery.com/posts/building-future-facing-frontend-architectures"&gt;component composition patterns&lt;/a&gt; in combination with &lt;a href="https://reactjs.org/docs/lifting-state-up.html"&gt;lifting state up&lt;/a&gt; can get you pretty far.&lt;/p&gt;

&lt;p&gt;For remote server cache state there are common problems like request de-duplication, retries, polling, handling mutations and the list goes on.&lt;/p&gt;

&lt;p&gt;As applications grow Redux tends to want to suck up all the state regardless of it's type, as it promotes a single store. &lt;/p&gt;

&lt;p&gt;This commonly lead to storing all the things in a big monolithic store. Which often times exacerbated the second problem of optimizing run-time performance.&lt;/p&gt;

&lt;p&gt;Because Redux handles the global shared state generically, a lot of these sub problems needed to be repeatedly solved (or often times just left un-attended). &lt;/p&gt;

&lt;p&gt;This lead to big monolithic stores holding everything between UI and remote entity state being managed in a single place. &lt;/p&gt;

&lt;p&gt;This of course becomes very difficult to manage as things grow. Especially on teams where frontend developers need to ship fast. Where working on decoupled independent complex components becomes necessary.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The de-emphasis of Redux
&lt;/h3&gt;

&lt;p&gt;As we encountered more of these pain points, over time defaulting to Redux when spinning up a new project became discouraged. &lt;/p&gt;

&lt;p&gt;In reality a lot of web applications are CRUD (create, read, update and delete) style applications that mainly need to synchronize the frontend with remote state data. &lt;/p&gt;

&lt;p&gt;In other words, the main problems worth spending time on is the set of remote server cache problems. These problems include how to fetch, cache and synchronize with server state. &lt;/p&gt;

&lt;p&gt;It also includes many other problems like handling race conditions, invalidating and refetching of stale data, de-duplicating requests, retries, refetching on component re-focus, and ease in mutating remote data compared to the boilerplate usually associated with Redux. &lt;/p&gt;

&lt;p&gt;The boilerplate for this use-case was unnecessary and overly complex. Especially so when commonly combined with middleware libraries like &lt;code&gt;redux-saga&lt;/code&gt; and &lt;code&gt;redux-observable&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This toolchain was overkill for these types of applications. Both in terms of the overhead sent down to the client for fetching and mutations but in complexity of the model being used for relatively simple operations.  &lt;/p&gt;

&lt;h3&gt;
  
  
  The pendulum swing to simpler approaches
&lt;/h3&gt;

&lt;p&gt;Along came hooks and the new context API. For a time being the pendulum swang back from heavy abstractions like Redux to utilizing native context with the new hooks APIs. This often involved simple &lt;code&gt;useContext&lt;/code&gt; combined with &lt;code&gt;useState&lt;/code&gt; or &lt;code&gt;useReducer&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This is a fine approach for simple applications. And a lot of smaller applications can get away with this. However as things grow, this lead to two problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Re-inventing Redux.&lt;/strong&gt; And often times falling into the many problems we defined before. And either not solving them, or solving them poorly compared to a library dedicated to solving those specific edge cases. Leading many feeling the need to the promote the idea that &lt;a href="https://blog.isquaredsoftware.com/2021/01/context-redux-differences/"&gt;React context has nothing to do with state management&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optimizing runtime performance.&lt;/strong&gt; The other core problem is optimizing re-renders. Which can be difficult to get right as things scale when using native context. &lt;/p&gt;

&lt;p&gt;It's worth noting modern user-land libraries such as &lt;code&gt;useContextSelector&lt;/code&gt; designed to help with this problem. With the React team starting to look at &lt;a href="https://github.com/reactjs/rfcs/pull/119#issuecomment-586390430"&gt;addressing this pain point automatically in the future as part of React&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The rise of purpose built libraries to solve the remote state management problem
&lt;/h3&gt;

&lt;p&gt;For most web applications that are CRUD style applications, local state combined with a dedicated remote state management library can get you very far. &lt;/p&gt;

&lt;p&gt;Some example libraries in this trend include &lt;a href="https://react-query.tanstack.com/overview"&gt;React query&lt;/a&gt;, &lt;a href="https://github.com/vercel/swr"&gt;SWR&lt;/a&gt;, &lt;a href="https://www.apollographql.com/"&gt;Apollo&lt;/a&gt; and &lt;a href="https://relay.dev/"&gt;Relay&lt;/a&gt;. Also in a "reformed" Redux with &lt;a href="https://redux-toolkit.js.org/"&gt;Redux Toolkit&lt;/a&gt; and &lt;a href="https://redux-toolkit.js.org/introduction/getting-started#rtk-query"&gt;RTK Query&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These are purpose built to solve the problems in the remote data problem space that often times were too cumbersome to implement solely using Redux. &lt;/p&gt;

&lt;p&gt;While these libraries are great abstractions for single page apps. They still require a hefty overhead in terms of Javascript needed over the wire. Required for fetching and data mutation. And as a community of web builders the &lt;a href="https://v8.dev/blog/cost-of-javascript-2019"&gt;real cost of Javascript&lt;/a&gt; is becoming more fore-front of mind.&lt;/p&gt;

&lt;p&gt;It's worth noting newer meta-frameworks like &lt;a href="https://remix.run/"&gt;Remix&lt;/a&gt; address this, by providing abstractions for server-first data loading and declarative mutations that don't require downloaded a dedicated library. Extending the "UI as a function of state" concept &lt;a href="https://remix.run/blog/remix-data-flow"&gt;beyond the just the client&lt;/a&gt; to include the backend remote state data. &lt;/p&gt;

&lt;h2&gt;
  
  
  The new wave of global state management libraries and patterns
&lt;/h2&gt;

&lt;p&gt;For large applications there is often no avoiding needing to have shared global state that is distinct from remote server state. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rise of bottom up patterns&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can see previous state management solutions like Redux as somewhat "top down" in their approach. That over time tends to want to suck up all the state at the top of the component tree. State lives up high in the tree, and components below pull down the state they need through selectors.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://frontendmastery.com/posts/building-future-facing-frontend-architectures"&gt;Building future facing frontend architectures&lt;/a&gt; we saw the usefulness of the bottom-up view to constructing components with composition patterns. &lt;/p&gt;

&lt;p&gt;Hooks both afford and promote the same principle of composable pieces put together to form a larger whole.  With hooks we can mark a shift from monolithic state management approaches with a giant global store. Towards a bottom-up "micro" state management with an emphasis of smaller state slices consumed via hooks.&lt;/p&gt;

&lt;p&gt;Popular libraries like &lt;a href="https://recoiljs.org/"&gt;Recoil&lt;/a&gt; and &lt;a href="https://jotai.org/"&gt;Jotai&lt;/a&gt; exemplify this bottom-up approach with their concepts of "atomic" state. &lt;/p&gt;

&lt;p&gt;An atom is a minimal, but complete unit of state. They are small pieces of state that can connect together to form new derived states. That ends up forming a graph. &lt;/p&gt;

&lt;p&gt;This model allows you to build up state incrementally bottom up. And optimizes re-renders by only invalidating atoms in the graph that have been updated.&lt;/p&gt;

&lt;p&gt;This in contrast to having one large monolithic ball of state that you subscribe to and try to avoid unnecessary re-renders.&lt;/p&gt;

&lt;h3&gt;
  
  
  How modern libraries address the core problems of state management
&lt;/h3&gt;

&lt;p&gt;Below is a simplified summary of the different approaches each "new wave" library takes to solve each of the core problems of state management. These are the same problems we defined at the start of the article.&lt;/p&gt;

&lt;h4&gt;
  
  
  Ability to read stored state from anywhere within a subtree
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Simplified API example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;React-Redux&lt;/td&gt;
&lt;td&gt;React lifecycle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useSelector(state =&amp;gt; state.foo)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recoil&lt;/td&gt;
&lt;td&gt;React lifecycle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&lt;code&gt;const todos = atom({ key: 'todos', default: [] })&lt;/code&gt;&lt;br&gt; const todoList = useRecoilValue(todos)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jotai&lt;/td&gt;
&lt;td&gt;React lifecycle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&lt;code&gt;const countAtom = atom(0)&lt;/code&gt;&lt;br&gt;&lt;code&gt;const [count, setCount] = useAtom(countAtom)&lt;/code&gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valtio&lt;/td&gt;
&lt;td&gt;Module state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&lt;code&gt;const state = proxy({ count: 0 })&lt;/code&gt;&lt;br&gt;const snap = useSnapshot(state) &lt;br&gt; state.count++&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Ability to write and update stored state
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Update API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;React-Redux&lt;/td&gt;
&lt;td&gt;Immutable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recoil&lt;/td&gt;
&lt;td&gt;Immutable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jotai&lt;/td&gt;
&lt;td&gt;Immutable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zustand&lt;/td&gt;
&lt;td&gt;Immutable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valtio&lt;/td&gt;
&lt;td&gt;Mutable style&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Runtime performance re-render optimizations
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Manual optimizations&lt;/strong&gt; often mean the creation of selector functions that subscribe to a specific piece of state. The advantage here is that consumers can have fine-grained control of how to subscribe and optimize how components that subscribe to that state will re-render. A disadvantage is that this is a manual process, that can be error prone, and one might argue requires an unnecessary overhead that shouldn't be part of the API. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic optimizations&lt;/strong&gt; is where the library optimizes this process of only re-rendering what is necessary, automatically, for you as a consumer. The advantage here of course is the ease of use, and the ability for consumers to focus on developing features without needing to worry about manual optimizations. A disadvantage of this is that as a consumer the optimization process is a black box, and without escape hatches to manually optimize some parts may feel a bit too magic.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;React-Redux&lt;/td&gt;
&lt;td&gt;Manual via selectors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recoil&lt;/td&gt;
&lt;td&gt;Semi-manual through subscriptions to atoms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jotai&lt;/td&gt;
&lt;td&gt;Semi-manual through subscriptions to atoms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zustand&lt;/td&gt;
&lt;td&gt;Manual via selectors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valtio&lt;/td&gt;
&lt;td&gt;Automatic via &lt;code&gt;Proxy&lt;/code&gt; snapshots&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Memory optimizations
&lt;/h4&gt;

&lt;p&gt;Memory optimizations tend to only be issues on very large applications. A big part of this will depend on whether or not the library stores state at the module level or within the React runtime. It also depends how you structure the store. &lt;/p&gt;

&lt;p&gt;The benefit of smaller independent stores compared to large monolithic ones is they can be garbage collected automatically when all subscribing components unmount. Whereas large monolithic stores are more prone to memory leaks without proper memory management.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redux&lt;/td&gt;
&lt;td&gt;Needs to be managed manually&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recoil&lt;/td&gt;
&lt;td&gt;Automatic - as of v0.3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jotai&lt;/td&gt;
&lt;td&gt;Automatic - atoms are stored as keys in a &lt;code&gt;WeakMap&lt;/code&gt; under the hood&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zustand&lt;/td&gt;
&lt;td&gt;Semi-automatic - API's are available to aid in manually unsubscribing components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valtio&lt;/td&gt;
&lt;td&gt;Semi-automatic - Garbage collected when subscribing components unmount&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Concluding thoughts
&lt;/h2&gt;

&lt;p&gt;There's no right answer as to what is the best global state management library. A lot will depend on the needs of your specific application and who is building it. &lt;/p&gt;

&lt;p&gt;Understanding the underlying unchanging problems state management libraries need to solve can help us assess both the libraries of today and the ones that will be developed in the future. &lt;/p&gt;

&lt;p&gt;Going into depth on specific implementations is outside the scope of this article. If you're interested to dig deeper I can recommend &lt;a href="https://twitter.com/dai_shi"&gt;Daishi Kato's&lt;/a&gt; &lt;a href="https://github.com/PacktPublishing/Micro-State-Management-with-React-Hooks"&gt;React state management book&lt;/a&gt;, which is a good resource to go deeper into specific side-by-side comparisons of some of the newer libraries and approaches mentioned in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.soundcloud.com/blog/garbage-collection-in-redux-applications"&gt;Garbage Collection in Redux Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=lGEMwh32soc"&gt;React without memo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react-redux.js.org/api/hooks#stale-props-and-zombie-children"&gt;The zombie child problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/reactwg/react-18/discussions/86"&gt;useMutableSource -&amp;gt; useSyncExternalStore discussion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dai-shi/proxy-compare"&gt;Proxy compare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dai-shi/use-context-selector"&gt;useContextSelector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://remix.run/blog/remix-data-flow"&gt;Data flow in Remix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building future facing frontend architectures</title>
      <dc:creator>rem</dc:creator>
      <pubDate>Mon, 13 Jun 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/remrem/building-future-facing-frontend-architectures-28i2</link>
      <guid>https://dev.to/remrem/building-future-facing-frontend-architectures-28i2</guid>
      <description>&lt;p&gt;Building frontend architectures that are performant and easy to change is hard at scale. &lt;/p&gt;

&lt;p&gt;In this guide we'll explore the main ways complexity can rapidly and silently compound in frontend projects worked on by many developers and teams. &lt;/p&gt;

&lt;p&gt;We'll also look at effective ways to avoid getting overwhelmed in that complexity. Both before it's a problem, and after if you find yourself thinking "oh shit, how did this end up getting so complicated?" when you're tasked to add or change a feature.&lt;/p&gt;

&lt;p&gt;Frontend architecture is a broad topic with many various aspects. This guide will focus specifically on component code structure that results in resilient frontends that can easily adapt to changes.&lt;/p&gt;

&lt;p&gt;The examples given in this guide use React. But the underlying principles can apply to any component based framework. &lt;/p&gt;

&lt;p&gt;We'll start from the very beginning. On how the structure of our code is influenced, even before any code is written.&lt;/p&gt;

&lt;h2&gt;
  
  
  The influence of common mental models
&lt;/h2&gt;

&lt;p&gt;The mental models we have, how we think about things, end up influencing our decisions to a large extent.&lt;/p&gt;

&lt;p&gt;In large codebases, it's the culmination of these many decisions being constantly made that result in it's overall structure.&lt;/p&gt;

&lt;p&gt;When we build stuff as a team, it’s important to make explicit the models we have, and expect others to have. Because everyone usually has their own implicit ones.  &lt;/p&gt;

&lt;p&gt;That's why teams end up needing things like shared style guides and tools like &lt;a href="https://prettier.io/"&gt;prettier&lt;/a&gt;. So as a group, we have a shared model of how things should be consistent, what things are, and where things should go. &lt;/p&gt;

&lt;p&gt;This makes life much easier. It allows us to avoid the descent into an unmaintainable code base over time with everyone going down their own path.&lt;/p&gt;

&lt;p&gt;If you've experienced a project under rapid development by many developers eager to ship, you might have seen how fast things can get out of hand without proper guidelines. And how over time the frontend can get slower and slower as more code is added and runtime performance deteriorates.&lt;/p&gt;

&lt;p&gt;In the next few sections we'll look answering the following questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What are the most common mental models to have when developing frontend applications using a component based model frameworks like React use? &lt;/li&gt;
&lt;li&gt;How do they influence how we structure our components?&lt;/li&gt;
&lt;li&gt;What trade-offs are implicit in them, that we can make explicit, that lead to the rapid rise of complexity?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Thinking in components
&lt;/h2&gt;

&lt;p&gt;React is the most popular component based frontend framework. &lt;a href="https://reactjs.org/docs/thinking-in-react.html"&gt;"Thinking in react"&lt;/a&gt;  is usually the first article you read when first getting started. &lt;/p&gt;

&lt;p&gt;It lays out the key mental models on how to think when building frontend applications "the React way". It's a good article because the advice can also apply to any component based framework.&lt;/p&gt;

&lt;p&gt;The main principles it lays out allow you to ask the following questions, whenever you need to build a component.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What is the one responsibility of this component?&lt;/strong&gt; Good component API design naturally follows the single responsibility principle, which is important for composition patterns. It’s easy to conflate something simple as easy. As requirements come in and change, keeping things simple is often quite hard as we’ll explore later in the guide.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What’s the absolute minimum, but complete, representation of its state?&lt;/strong&gt; The idea is that it’s better to start with the smallest but complete source of truth for your state, that you can derive variations from.&lt;br&gt;
This is flexible, simple, and avoids common data synchronization mistakes such updating one piece of state but not the other.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Where should the state live?&lt;/strong&gt; State management is a broad topic outside the scope of this guide. But generally, if a state can be made local to a component, then it should be. The more components depend on global state internally the less reusable they become. Asking this question is useful to identify what components should depend on what state.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some more wisdom from the article:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A component should ideally only do one thing. If it ends up growing, it should be decomposed into smaller sub components.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The principles are outlined here are simple, battle-tested, and they work for taming complexity. They form the basis for the most common mental model when creating components. &lt;/p&gt;

&lt;p&gt;Simple doesn't mean easy though. In practice this is much easier said than done in the context of large projects with multiple teams and developers.&lt;/p&gt;

&lt;p&gt;Successful projects often come about from sticking to basic principles well, and consistently. And not making too many costly mistakes. &lt;/p&gt;

&lt;p&gt;This brings up two questions we'll explore. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;What are the circumstances that prevent the application of these simple principles? &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How can we mitigate those circumstances as best as possible?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Below we'll see why over time maintaining simplicity is not always so straight forward in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Top down vs bottom up
&lt;/h2&gt;

&lt;p&gt;Components are the core unit of abstraction in modern frameworks like React. There's two main ways to think about creating them. Here's what thinking in React has to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can build top-down or bottom-up. That is, you can either start with building the components higher up in the hierarchy. In simpler examples, it’s usually easier to go top-down, and on larger projects, it’s easier to go bottom-up and write tests as you build.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;More solid advice. At first glance it sounds simple. Like reading "single responsibility is good" it's easy to nod along and move on. &lt;/p&gt;

&lt;p&gt;But the distinction between a top-down mental model and bottom-up one, is much more significant than it seems on the surface. When applied at scale, both modes of thinking lead to very different outcomes when one is shared widely as an implicit way of building components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building top down
&lt;/h2&gt;

&lt;p&gt;Implied in the quote above is a trade-off between ease in making progress by taking a top down approach for simpler examples, versus a slower more scalable bottom up approach for large projects. &lt;/p&gt;

&lt;p&gt;Top down is generally the most intuitive and straight forward approach. In my experience it's the most common mental model developers working on feature development tend to have when structuring components. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does a top down approach look like?&lt;/strong&gt; The common advice when given a design to build is to “draw boxes around the UI, these will become your components”.&lt;/p&gt;

&lt;p&gt;This forms the basis for the top level components we end up creating. With this approach we often create a coarse grained component to begin with. With what seems like the right boundaries to get started with.&lt;/p&gt;

&lt;p&gt;Let’s say we get a design for a new admin admin dashboard we need to build.  We go ahead and look at the designs to see what components we'll need to make. &lt;/p&gt;

&lt;p&gt;It has a new sidebar nav in the design. We draw a box around the sidebar, and create a story that tells the developers to create the new &lt;code&gt;&amp;lt;SideNavigation /&amp;gt;&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;Following this top down approach, we may think about what props it takes, and how it renders. Let's assume we get the list of the nav items from a backend API. Following our implicit top down model, it wouldn't be surprising to see an initial design something like in the pseudo code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// get list from API call somewhere up here &lt;/span&gt;
    &lt;span class="c1"&gt;// and then transform into a list we pass to our nav component&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;navItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dashboards&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboards&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Settings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/settings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SideNavigation&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;navItems&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our top down approach so far seems fairly straight forward and intuitive. Our intention is to make things easy and reusable, consumers just need to pass in the items they want rendered and our &lt;code&gt;SideNavigation&lt;/code&gt; will handle it for them. &lt;/p&gt;

&lt;p&gt;Some things to note that are common in top down approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We started building at the top level boundary we initially identified as the component we'll need. From the box we drew around in the design.&lt;/li&gt;
&lt;li&gt;It's a singular abstraction that handles all the things related to the side navigation bar.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It's API is often "top down" in the sense that consumers pass down the data it needs to work through the top and it handles everything under the hood. &lt;/p&gt;

&lt;p&gt;Often times our components render data directly from a backend data source, so this fits that same model of passing the data "down" into the components to render.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For smaller projects, there's nothing necessarily wrong with this approach. For large codebases with many developers trying to ship fast, we'll see how a top down mental model gets quickly problematic at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where top down goes wrong
&lt;/h3&gt;

&lt;p&gt;A top down mode of thinking tends to fix itself on a particular abstraction out of the gate to solve the immediate problem at hand.&lt;/p&gt;

&lt;p&gt;It's intuitive. It often feels like the most straight forward approach to building components. It also often leads to APIs that optimize for &lt;em&gt;initial&lt;/em&gt; ease of consumption.&lt;/p&gt;

&lt;p&gt;Here’s a somewhat common scenario. You're on a team, on a project that is under rapid development. You've drawn your boxes and created the story, and now you've merged your new component. A new requirement comes along that requires you to update the side navigation component.&lt;/p&gt;

&lt;p&gt;Here's when things can start to get hairy fast. It's a common set of circumstances which can lead to the creation of large, monolithic components.&lt;/p&gt;

&lt;p&gt;A developer picks up the story to make the change. They arrive at the scene, ready to code. They’re in the context of the abstraction and API having already been decided.&lt;/p&gt;

&lt;p&gt;Do they:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A&lt;/strong&gt; - Think about whether or not this is the right abstraction. If not, undo it by actively decomposing it before doing the work outlined in their story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B&lt;/strong&gt; - Add an additional property. Add the new functionality behind a simple conditional that checks for that property. Write a few tests that pass the new props. It works and is tested. And as a bonus it was done fast.&lt;/p&gt;

&lt;p&gt;As Sandy Mets puts it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Existing code exerts a powerful influence. Its very presence argues that it is both correct and necessary. We know that code represents effort expended, and we are very motivated to preserve the value of this effort. And, unfortunately, the sad truth is that the more complicated and incomprehensible the code, i.e. the deeper the investment in creating it, the more we feel pressure to retain it (the "sunk cost fallacy")&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The sunk cost fallacy exists because we are naturally more acute to avoiding loss. When you add time pressure, either from a deadline, or just simply "the story point is a 1". The odds likely are against you (or your team mates) from chosing &lt;strong&gt;A&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At scale it's these rapid culmination of these smaller decisions that add up quickly and start to compound the complexity of our components.&lt;/p&gt;

&lt;p&gt;Unfortunately we've now failed one of the foundational principles outlined in “Thinking in React”. The easy thing to do, does not often lead to simplicity. And the thing that leads us to simplicity is not easy to do, compared with the alternatives.&lt;/p&gt;

&lt;p&gt;Caveats&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Again context matters here, if you’re in a rush to ship an MVP that was due yesterday, do what you have to do to keep the business or project alive. Technical debt is a trade off and situations call for taking it on.&lt;/li&gt;
&lt;li&gt;But if you’re working on a product with many teams contributing to it, that has a long term plan, thinking about effective decomposition through continual refactoring is critically important to longevity.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's apply this common scenario on to our simple navigation sidebar example. &lt;/p&gt;

&lt;p&gt;The first design change comes around. And we need to add the requirement for nav items to have icons, different size texts and for some of them to be links rather than SPA page transitions.  &lt;/p&gt;

&lt;p&gt;In practice UI holds a lot of visual state. we also want to have things like separators, opening links in a new tab, some to have selected default state, and so on and so forth.&lt;/p&gt;

&lt;p&gt;Because we pass down the list of nav items as an array to the side bar component, for each of these new requirements, we need to add some additional properties on those objects to distinguish between the new types of nav items and their various states.&lt;/p&gt;

&lt;p&gt;So our type for our now might look something like with type corresponding to whether its a link or a regular nav item:&lt;br&gt;
&lt;code&gt;{  id, to, label, icon, size, type, separator, isSelected }&lt;/code&gt; etc.&lt;/p&gt;

&lt;p&gt;And then inside the &lt;code&gt;&amp;lt;SideNavigation /&amp;gt;&lt;/code&gt; we'll have to check the &lt;code&gt;type&lt;/code&gt; and render the nav items based on that. A small change like this is already starting to get a bit of a smell. &lt;/p&gt;

&lt;p&gt;The problem here is top down components with APIs like this, have to respond to changes in requirements by adding to the API, and forking logic internally based on what is passed in. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;From little things big things grow&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A few weeks later a new feature is being requested and there is the requirement for being able to click on a nav item and to transition into a nested sub navigation under that item, with a back button to go back to main navigation list. We also want the ability for admins to be able to reorder the navigation items via drag and drop.&lt;/p&gt;

&lt;p&gt;We now need to have the concept of nesting lists and associating sub lists with parent ones, and some items being &lt;code&gt;draggable&lt;/code&gt; or not.&lt;/p&gt;

&lt;p&gt;A few requirements changes and you can see how things start to get complicated. &lt;/p&gt;

&lt;p&gt;What started as a relatively simple component with a simple API quickly grows into something else within  a few quick iterations. Let's say our developer manages to get things working in time.&lt;/p&gt;

&lt;p&gt;At this point the next developer or team who needs to use or adapt this component is dealing with a monolithic component that requires a complex configuration, that is (let's be real) most likely poorly documented if at all. &lt;/p&gt;

&lt;p&gt;Our initial intention of "just pass down the list and the component will take care of the rest" has back fired at this point, and the component is both slow and risky to make changes to.&lt;/p&gt;

&lt;p&gt;A common scenario at this point is considering throwing everything away and rewriting the component from scratch. Now that we understand the problems and use-cases it needs to solve from the first round of iterations.&lt;/p&gt;
&lt;h3&gt;
  
  
  The organic growth of monolithic components
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Everything should be built top-down, except the first time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As we've seen monolithic components are components that try to do too much. They take in too much data, or configuration options through props, manage too much state, and output too much UI.&lt;/p&gt;

&lt;p&gt;They often start as simple components, and through the organic growth of complexity as described above, which is more common, end up doing too much over time.&lt;/p&gt;

&lt;p&gt;What started as a simple component, within a few iterations (even within the same sprint) as you build the new features can be on the way to becoming a monolithic component.&lt;/p&gt;

&lt;p&gt;When this happens to multiple components as teams work on the same codebase under rapid development, the frontend quickly becomes harder to change and slower end for users.&lt;/p&gt;

&lt;p&gt;Here are some other ways monolithic components can lead to things silently imploding.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;They arise through premature abstraction.&lt;/strong&gt; There is one other subtle gotcha that leads to monolithic components. Related to some common models that get instilled early on as software developers. Particularly the adherence to DRY (don't repeat yourself).&lt;/p&gt;

&lt;p&gt;The fact that DRY is engrained early, and we see a small amount of duplication at the sites where components are being composed. It’s easy to think "that's getting duplicated a lot, it would be good to abstract that into a single component" and we rush into a premature abstraction.&lt;/p&gt;

&lt;p&gt;Everything's a trade-off, but it's far easier to recover from no abstraction than the wrong abstraction. And as we’ll discuss further below starting with a bottom up model allows us to arrive at those abstractions organically, allowing us to avoid creating them prematurely.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;They prevent code re-use across teams.&lt;/strong&gt; You’ll often discover another team has implemented, or is working on, something similar to &lt;br&gt;
what your team needs.&lt;/p&gt;

&lt;p&gt;In  most cases it’ll do 90% of what you want, but you want some slight variation.&lt;br&gt;
Or you just want to re-use a specific part of it’s functionality without having to take the whole thing on.&lt;/p&gt;

&lt;p&gt;If it's a monolithic “all or nothing” component like our &lt;code&gt;&amp;lt;SideNavigation /&amp;gt;&lt;/code&gt; it will be harder to leverage that existing work. Rather than taking on the risk of refactoring or decomposing someone else's package. It often becomes easier to just re-implement and fork it into the safety of your own package. Leading to multiple duplicated components all with slight variations and suffering from the same problems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;They bloat bundle sizes.&lt;/strong&gt; How do we allow only the code that needs to be loaded, parsed and ran at the right time? &lt;/p&gt;

&lt;p&gt;In practice there are some components that are more important to show users first. A key performance strategy for larger applications is the coordination of async loaded code in "phases" based on priority. &lt;/p&gt;

&lt;p&gt;In addition to giving components the ability to opt-in and out of being rendered on the server (because ideally we perform the server side rendering as fast as possible only with components that will actually be seen by the user on first paint). The idea here is to defer when possible.&lt;/p&gt;

&lt;p&gt;Monolithic components prevent these efforts from happening because you have to load everything as one big chunky component. Rather than having independent components that can be optimized and only loaded when truly needed by the user. Where consumers only pay the performance price of what they actually use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;They lead to poor runtime performance.&lt;/strong&gt; Frameworks like React that have a simple functional model of state -&amp;gt; UI are incredibly productive. But the reconciliation process to see what has changed in the virtual DOM is expensive at scale. Monolithic components make it very difficult to ensure only the minimal amount of things are re-rendering when that state changes.&lt;/p&gt;

&lt;p&gt;One of the simplest ways to achieve better rendering performance in a framework like React that as a virtual DOM is to separate the components that change from the ones that do change. &lt;/p&gt;

&lt;p&gt;So you when state changes you only re-renders only what is strictly necessary. If you use a declarative data fetching framework like Relay, this technique becomes more and more important to prevent expensive re-rendering of sub-trees when data updates happen.&lt;/p&gt;

&lt;p&gt;Within monolithic components and top down approaches in general, finding this split is difficult, error prone, and often leads to over use of &lt;a href="https://overreacted.io/before-you-memo/"&gt;memo()&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Building bottom up
&lt;/h2&gt;

&lt;p&gt;Compared to a top down approach, going bottom up is often less intuitive and can be initially slower. It leads to multiple smaller components whose APIs are reusable. Instead of big kitchen sink style components. &lt;/p&gt;

&lt;p&gt;When you're trying to ship fast this is an unintuitive approach because not every component needs to be reusable in practice. &lt;/p&gt;

&lt;p&gt;However creating components whose APIs could be reusable even if they aren't, generally leads to much more readable, testable, changeable and deletable component structures.&lt;/p&gt;

&lt;p&gt;There's no one correct answer on how far things should be broken down. The key to managing this is use the single responsibility principle as a general guideline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is a bottom up mental model different to top down?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Going back to our example. With a bottom up approach we are still likely to create a top level &lt;code&gt;&amp;lt;SideNavigation /&amp;gt;&lt;/code&gt; but it's how we build up to it that makes all the difference.&lt;/p&gt;

&lt;p&gt;We identify the top level &lt;code&gt;&amp;lt;SideNavigation /&amp;gt;&lt;/code&gt; but the difference is our work doesn't begin there. &lt;/p&gt;

&lt;p&gt;It begins by cataloging all the underlying elements that make up the functionality of the &lt;code&gt;&amp;lt;SideNavigation /&amp;gt;&lt;/code&gt; as a whole, and constructing those smaller pieces that can then be composed together. In this way it's slightly less intuitive when getting started.&lt;/p&gt;

&lt;p&gt;The total complexity is distributed among many smaller single responsibility components, rather than a single monolithic component.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does a bottom up approach look like?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's go back to the side navigation example. Here's an example of what the simple case might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SideNavigation&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NavItem&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/NavItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NavItem&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/settings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/NavItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SideNavigation&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing remarkable there in the simple case. What would the API look like to support nested groups?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SideNavigation&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Section&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NavItem&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/NavItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NavItem&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/projects&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Projects&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/NavItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Separator&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NavItem&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/settings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/NavItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LinkItem&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/foo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/NavItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Section&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NestedGroup&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NestedSection&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My projects&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NavItem&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/project-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Project&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/NavItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NavItem&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/project-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Project&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/NavItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NavItem&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/project-3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Project&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/NavItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LinkItem&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/foo.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;See&lt;/span&gt; &lt;span class="nx"&gt;documentation&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/LinkItem&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/NestedSection&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/NestedGroup&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/SideNavigation&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The end result of a bottom up approach is intuitive. It takes more upfront effort as the complexity of the simpler API is encapsulated behind the individual components. But that's what makes it a more consumable and adaptable long term approach.&lt;/p&gt;

&lt;p&gt;The benefits compared to our top down approach are many:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Different teams that use the component only pay for the components they actually import and use. &lt;/li&gt;
&lt;li&gt;We can also easily code split and async load elements that are not an immediate priority for the user.&lt;/li&gt;
&lt;li&gt;Rendering performance is better and easier to manage because the only the sub-trees that change due to an update need to re-render.&lt;/li&gt;
&lt;li&gt;We can create and optimize individual components that have a specific responsibility within the nav. It's also more scalable from a code structure point of view, because each component can be worked on and optimized in isolation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What's the catch?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bottom-up is initially slower, but in the long term faster, because it’s more adaptable. You can more easily avoid hasty abstractions and instead ride the wave of changes over time until the right abstraction becomes obvious. It’s the best way to prevent the spread of monolithic components.&lt;/p&gt;

&lt;p&gt;If it's a shared component used across the codebase like our sidebar nav, building bottom up often requires slightly more effort for the consumer side of things to assemble the pieces. But as we've seen this is a trade off worth making in large projects with many shared components. &lt;/p&gt;

&lt;p&gt;The power of a bottom-up approach is that your model starts with the premise “what are the simple primitives I can compose together to achieve what I want” versus starting out with a particular abstraction already in mind.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"One of the most important lessons of Agile software development is the value of iteration; this holds true at all levels of software development, including architecture"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A bottom up approach allows you to iterate better in the long term. &lt;/p&gt;

&lt;p&gt;Next let's recap some useful principles to keep in mind that make build this way easier:&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategies for avoiding monolithic components
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Balancing single responsibility vs DRY.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Thinking bottom up often means embracing composition patterns. Which often means at the points of consumption there can be some duplication. &lt;/p&gt;

&lt;p&gt;DRY is the first thing we learn as developers and it feels good to DRY up code. But it’s often better to wait and see if it’s needed before making everything DRY.  &lt;/p&gt;

&lt;p&gt;But this approach lets you “ride the wave of complexity” as the project grows and requirements change, and allows abstract things for easier consumption at the time it makes sense to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inversion of control&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A simple example to understand this principle is the difference between callbacks and promises.&lt;/p&gt;

&lt;p&gt;With callbacks you won’t necessarily know where that function is going, how many times it will be called, or with what.&lt;/p&gt;

&lt;p&gt;Promises invert the control back to the consumer so you can start composing your logic and pretend as if the value was already there.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// may not know onLoaded will do with the callback we pass it&lt;/span&gt;
    &lt;span class="nx"&gt;onLoaded&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;doSomethingWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// control stays with us to start composing logic as if the&lt;/span&gt;
    &lt;span class="c1"&gt;// value was already there&lt;/span&gt;
    &lt;span class="nx"&gt;onLoaded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stuff&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;doSomethingWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stuff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;In the context of React, we can see this achieved through component API design.&lt;/p&gt;

&lt;p&gt;We can expose “slots” through &lt;code&gt;children&lt;/code&gt;, or render style props that maintain the inversion of control on the consumers side.&lt;/p&gt;

&lt;p&gt;Sometimes there is an aversion to inversion on control in this regard, because there is the feeling consumers will have to do more work. But this is both about giving up the idea you can predict future, and opting to empower consumers with flexibility.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // A "top down" approach to a simple button API
    &amp;lt;Button isLoading={loading} /&amp;gt;

    // with inversion of control
    // provide a slot consumers can utilize how they see fit
    &amp;lt;Button before={loading ? &amp;lt;LoadingSpinner /&amp;gt; : null} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The second example is more both more flexible to changing requirements and more performant, because the &lt;code&gt;&amp;lt;LoadingSpinner /&amp;gt;&lt;/code&gt; no longer needs to be a dependency inside the Button package.&lt;/p&gt;

&lt;p&gt;You can see the subtle differences in top down versus bottom up here. In the first example we pass down data and let the component handle it. In the second example we have to do a bit more work but ultimately it's a more flexible and performant approach.&lt;/p&gt;

&lt;p&gt;It's also interesting to note that &lt;code&gt;&amp;lt;Button /&amp;gt;&lt;/code&gt; itself could be composed from smaller primitives under the hood. Sometimes a particular abstraction has many different sub behavioral elements underneath that can be made explicit. &lt;/p&gt;

&lt;p&gt;For example we could break it down further into things like &lt;code&gt;Pressable&lt;/code&gt; that apply to both buttons and things like &lt;code&gt;Link&lt;/code&gt; components, that can combine to create things like a &lt;code&gt;LinkButton&lt;/code&gt;. This finer grained breakdown is usually left for the domain of design system libraries, but worth keeping in mind as product focussed engineers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Open for extension&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even when using composition patterns to build bottom up. You’ll still want to export specialized components with a consumable API, but built up from smaller primitives. For flexibility, you can also expose those smaller building blocks that make up that specialized component from your package as well.&lt;/p&gt;

&lt;p&gt;Ideally your components do one thing. So in the case of a pre-made abstraction, consumers can take that one thing they need and wrap it to extend with their own functionality. Alternatively they can just take a few primitives that make up that existing abstraction and construct what they need.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Leveraging storybook driven development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is usually a ton of discrete state that ends up getting managed in our components. State machine libraries are becoming increasingly popular for good reasons.&lt;/p&gt;

&lt;p&gt;We can adopt the models behind their thinking when building out our UI components in isolation with storybook and have stories for each type of possible state the component can be in. &lt;/p&gt;

&lt;p&gt;Doing it upfront like this can avoid you realizing that in production you forgot to implement a good error state. &lt;/p&gt;

&lt;p&gt;It also helps to identify all the sub components that will be needed to build up to the component that you are working on.&lt;/p&gt;


&lt;ul&gt;

        &lt;li&gt;Is it accessible?&lt;/li&gt;

        &lt;li&gt;What does this look like when it's loading?&lt;/li&gt;

        &lt;li&gt;What data does it depend on?&lt;/li&gt;

        &lt;li&gt;How does it handle errors?&lt;/li&gt;

        &lt;li&gt;What happens when only a partial amount of data is available?&lt;/li&gt;

        &lt;li&gt;What happens if you mount this component multiple times? In other words what kind of side effects does it have, and if it manages internal state would we expect that state to be consistent?&lt;/li&gt;

        &lt;li&gt;How does it handle “impossible states” and the transitions between those states. E.g if it has a &lt;code&gt;loading&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt; props what happens if they are both &lt;code&gt;true&lt;/code&gt;? (In this example it’s probably an opportunity to rethink the component API)&lt;/li&gt;

        &lt;li&gt;How composable is it? Thinking about its API.&lt;/li&gt;

        &lt;li&gt;Are there any opportunities for delight here? E.g subtle animations done well.&lt;/li&gt;

    &lt;/ul&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some more common situations to avoid that prevent building resilient components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Name components based on what they actually do.&lt;/strong&gt; Comes back to the single responsibility principle. Don't be afraid of long names if they make sense.&lt;/p&gt;

&lt;p&gt;It’s also easy to name a component slightly more generic than what is actually does. When things are named more generically than what they actually do, it indicates to other developers that it is the abstraction that handle everything related to X. &lt;/p&gt;

&lt;p&gt;So naturally when new requirements comes it stands out as the obvious place to do the change. Even when it might not make sense to do so.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Avoid prop names that contain implementation details.&lt;/strong&gt; Especially so with UI style "leaf" components. As much as you can it's good to avoid adding props like &lt;code&gt;isSomething&lt;/code&gt; where something is related to internal state or a domain specific thing. And then have that component do something different when that prop is passed in.&lt;/p&gt;

&lt;p&gt;If you need to do this, it’s clearer if the prop name reflects what it actually does in the context of that component consuming it.&lt;/p&gt;

&lt;p&gt;As an example, if the &lt;code&gt;isSomething&lt;/code&gt; prop ends up controlling something like padding, the prop name should reflect that instead, rather than have the component be aware of something seemingly unrelated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Be cautious of configuration via props.&lt;/strong&gt;  Comes back to inversion of control.&lt;/p&gt;

&lt;p&gt;Components like &lt;code&gt;&amp;lt;SideNavigation navItems={items} /&amp;gt;&lt;/code&gt; can work out fine if you know you’ll only have one type of child (and you know for certain this definitely won't change!) as they can also be typed safely.&lt;/p&gt;

&lt;p&gt;But as we've seen it's a pattern that's hard to scale across different teams and developers trying to ship fast. And in practice tend to be less resilient to change and tend to grow in complexity quickly.&lt;/p&gt;

&lt;p&gt;As you’ll often end up wanting to extend the component to have a different, or additional type of child. Which means you’ll add more stuff into those configuration options, or props, and add forking logic.&lt;/p&gt;

&lt;p&gt;Rather than have consumers arrange and pass in objects, a more flexible approach is to export the internal child component as well, and have consumers compose and pass components.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Avoid defining components in the render method.&lt;/strong&gt; Sometimes it might be common to have "helper" components within a component. These end up getting remounted on every render and can lead to some weird bugs.&lt;/p&gt;

&lt;p&gt;Additionally having multiple internal &lt;code&gt;renderX&lt;/code&gt;, &lt;code&gt;renderY&lt;/code&gt; methods tend to be a smell. These are usually a sign a component is becoming monolithic and is a good candidate for &lt;br&gt;
decomposition.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Breaking down monolithic components
&lt;/h2&gt;

&lt;p&gt;If possible refactor often and early. Identifying components likely to change and actively decomposing them is a good strategy to bake into your estimates.&lt;/p&gt;

&lt;p&gt;What do you do when you find yourself in a situation where the frontend has grown overly complex?&lt;/p&gt;

&lt;p&gt;There's usually two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Rewrite things and incrementally migrate to the new component&lt;/li&gt;
&lt;li&gt;Break down things down incrementally&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Going into component refactoring strategies is outside the scope of this guide for now. But there are a bunch of existing battle-tested refactoring patterns you can utilize. &lt;/p&gt;

&lt;p&gt;In frameworks like React, "components" are really just functions in disguise. Sp you can replace the word "function" with component in all the existing tried and true refactoring techniques.&lt;/p&gt;

&lt;p&gt;To give a few relevant examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://refactoring.com/catalog/removeFlagArgument.html"&gt;Remove Flag Argument&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html"&gt;Replace Conditional with Polymorphism&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://refactoring.com/catalog/pullUpField.html"&gt;Pull Up Field&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://refactoring.com/catalog/renameVariable.html"&gt;Rename Variable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://refactoring.com/catalog/inlineFunction.html"&gt;Inline Function&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Concluding thoughts
&lt;/h2&gt;

&lt;p&gt;We covered a lot of ground here. Let's recap the main takeaways from this guide.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The models we have affect the many micro-decisions we make when designing and building frontend components.&lt;/strong&gt; Making these explicit is useful because they accumulate pretty rapidly. The accumulation of these decisions ultimately determine what becomes possible - either increasing or reducing the friction to add new features or adopt new architectures that allow us to scale further (not sure about this point or merge it below).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Going top down versus bottom up when constructing components can lead to vastly different outcomes at scale&lt;/strong&gt;. A top down mental model is usually the most intuitive when building components. The most common model when it comes to decomposing UI, is to draw boxes around areas of functionality which then become your components. This process of functional decomposition is top down and often leads to the creation of specialized components with a particular abstraction straight away. Requirements will change. And within a few iterations it’s very easy for these components to rapidly become monolithic components.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Designing and building top down can lead to monolithic components.&lt;/strong&gt; A codebase full of monolithic components results in an end frontend architecture that is slow and not resilient to change. Monolithic components are bad because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They are expensive to change and maintain.&lt;/li&gt;
&lt;li&gt;They are risky to change.&lt;/li&gt;
&lt;li&gt;It’s hard to leverage existing work across teams.&lt;/li&gt;
&lt;li&gt;They suffer poor performance.&lt;/li&gt;
&lt;li&gt;They increase the friction when adopting future facing techniques and architectures that are important to continue scaling frontends such as effective code-splitting, code-reuse across teams, loading phases, rendering performance etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;We can avoid the creation of monolithic components&lt;/strong&gt; &lt;br&gt;
by understanding the underlying models and circumstances that often lead to the creation premature abstractions or the continued extension of them.&lt;/p&gt;

&lt;p&gt;React lends itself more effectively to a bottom up model when designing components. This more effectively allows you to avoid premature abstractions. Such that we can "ride the wave of complexity" and abstract when the time is right. Building this way affords more possibilities for component composition patterns to be realized. Being aware of how costly monolithic components truly are, we can apply standard refactoring practices to decompose them regularly as part of everyday product development.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Related readings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.geeksforgeeks.org/difference-between-bottom-up-model-and-top-down-model/"&gt;Difference between Bottom-Up Model and Top-Down Model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction"&gt;The wrong abstraction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kentcdodds.com/blog/inversion-of-control"&gt;Inversion of control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kentcdodds.com/blog/aha-programming"&gt;AHA programming&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>hooks</category>
      <category>state</category>
    </item>
    <item>
      <title>Frontend system design interviews - the definitive guide to approaching them</title>
      <dc:creator>rem</dc:creator>
      <pubDate>Fri, 27 May 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/remrem/frontend-system-design-interviews-the-definitive-guide-to-approaching-them-3kn5</link>
      <guid>https://dev.to/remrem/frontend-system-design-interviews-the-definitive-guide-to-approaching-them-3kn5</guid>
      <description>&lt;p&gt;Learn how to approach the frontend system design interview in 2022. A behind the scenes look at what big tech companies are looking for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Top tech companies are in desperate need of experienced frontend engineers. These companies are looking for frontenders that have knowledge in the architectural and software engineering principles, that years ago were mainly associated with backend developers.&lt;/p&gt;

&lt;p&gt;It's becoming more common for these companies to have system design rounds focussed on the frontend domain as part of their hiring process. These types of interviews are common for both experienced and entry-level roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  A lack of resources for frontenders
&lt;/h2&gt;

&lt;p&gt;The frontend system design interview shares many similarities with the traditional backend system design interview. Specifically in how they are structured. While there many great resources that focus on the backend domain, there seems to be few resources that go into depth on how to approach frontend system design interviews. In particular, resources that are grounded in what companies are actually looking for in candidates.&lt;/p&gt;

&lt;p&gt;The frontend world moves fast. Frameworks come and go, and patterns fall in and out of style as we learn together as a community. Despite the rapidly evolving frontend landscape, companies from startups to big tech companies are looking for a particular set of common attributes in candidates. &lt;/p&gt;

&lt;p&gt;These are the attributes that go beyond the current frontend technology trends. And make you an effective engineer whatever tech stack you find yourself using. Understanding how you can demonstrate these attributes can go a long way when practicing and preparing for the frontend system design interview.&lt;/p&gt;

&lt;h2&gt;
  
  
  About this guide
&lt;/h2&gt;

&lt;p&gt;This guide aims to provide a framework for the frontend system design interview, grounded in the specific attributes companies are looking for. &lt;/p&gt;

&lt;p&gt;The first half goes into detail on what each of these attributes are, and why they are important for frontend software engineers to have. Not surprisingly these attributes transcend just the interview process, and the frontend domain. They are also ones you can continually focus on developing as you progress towards more seniority as a frontend developer.&lt;/p&gt;

&lt;p&gt;The second half then provides the framework for approaching the frontend system design interview. And outlines how you can demonstrate each of these attributes as you move through the different stages of the interview.&lt;/p&gt;

&lt;p&gt;By the end of this guide you will have the knowledge on how to tackle and prepare for the frontend system design interview, and be equipped with the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Be aware of what kind of questions to expect in frontend system design interviews.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Know what specific signals an interviewer is actively looking for, that go beyond specific technologies, and how to prove you have them throughout the interview.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learn how to take ambiguous problem statements and break them down in to manageable chunks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Understand the heuristics for generating good questions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confidence approaching your next frontend system design interview knowing you can work through it step by step in a methodical way.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The attributes interviewers are looking for are necessary for frontend engineers to have at all levels, but especially so as you reach more senior levels in your career. The framework outlined in this guide also applies to approaching ambiguous problems more generally. &lt;/p&gt;

&lt;p&gt;With that out the way, let’s get into it and have you nailing your next frontend system design interview.&lt;/p&gt;

&lt;h2&gt;
  
  
  What companies are looking for behind the scenes
&lt;/h2&gt;

&lt;p&gt;We're about to dig into all the attributes top tech companies are looking for when hiring frontend engineers. They are attributes companies would expect an experienced frontend engineer to have. &lt;/p&gt;

&lt;p&gt;You might be stronger or weaker in some depending on what level you are interviewing for. But you’ll want to have these in the back of your mind when practicing, so you can communicate in such a way that shows you possess these attributes when going through the interview.&lt;/p&gt;

&lt;p&gt;As we will see further on, the interview can be broken down into distinct stages, and in each stage you'll have a chance to show these attributes to the interviewer. These attributes also tend to overlap with each other somewhat.&lt;/p&gt;

&lt;p&gt;These are the foundational "hard to fake" qualities of top engineers that companies naturally screen for as part of their hiring process. So let's see how they play out in the context of the frontend system design interview.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Problem solving ability&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Alright captain obvious. Let's break this down concretely. What this means in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your ability to deeply understand the problem/s enough that you can clearly articulate a specific problem statement/s to be solved.&lt;/li&gt;
&lt;li&gt;Your ability break down the problem into smaller distinct pieces. Such that they can be solved individually such that you can piece them back together to solve the larger specific problem you have articulated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Humans usually don’t solve vague problems all at once in a big chunk. So we'll need to start with a solid understanding the problem we are solving first. So we can then begin to identify how to break it down. &lt;/p&gt;

&lt;p&gt;We don't want to waste time creating solutions for problems that don't exist, or solutions to the wrong problem. &lt;/p&gt;

&lt;p&gt;In the context of the frontend system design interview, knowing what questions to ask in each phase of the interview is a core part of demonstrating this attribute. Understanding the problem space in detail begins with asking the right questions. Which then allows us to break down the problem further into smaller pieces. &lt;/p&gt;

&lt;p&gt;This also helps us know what things to optimize for when the time comes to make a difficult decision or trade off. Later on we will expand more on some guidelines on how to ask good questions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I had an hour to solve a problem and my life depended on it, I would use the first 55 minutes determining the proper question to ask, for once I know the proper question, I could solve the problem in less than five minutes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Technical proficiency and knowledge&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Frontend system design interviews are very open ended, and the initial question is often super vague. This is usually true for high level questions, but also true for lower level component design exercises. This is intentional because interviewers are assessing your ability to explore a problem space, as much as your ability to produce a solution. &lt;/p&gt;

&lt;p&gt;Before going into the interview, it’s a helpful framing to understand that there are no solutions, only trade-offs within a given context. Even when arriving at a specific solution, you'll want to understand its strengths and weaknesses. The use-cases it supports, and maintain an awareness of what it doesn't currently support.&lt;/p&gt;

&lt;p&gt;As you begin to break down the problem into separate smaller problems - the solutions to those smaller problems will in practice be solved by specific frontend patterns, technologies, protocols and techniques.&lt;/p&gt;

&lt;p&gt;As you identify solutions to problems, what’s important here is your ability to discuss, or at the very least mention, trade-offs upfront with multiple different approaches. Your technical knowledge and proficiency in the frontend domain allows you to make informed decisions regarding patterns, technologies and various architectures. All while understanding their limitations and strengths for the given problem at hand.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Ability to drive a solution to completion&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;At what granularity should we break things down to? How many possible solutions should we enumerate and analyze?&lt;/p&gt;

&lt;p&gt;While there is no "right" answer to those questions. A pragmatic answer is "enough so you can move forward confidently with the information you have available". Because there's always time constraints. &lt;/p&gt;

&lt;p&gt;In real world projects at tech companies there's usually tight deadlines. In frontend system design interviews it's around one hour. So as you break things down and enumerate potential solutions, you’ll need to make decisions and drive the interview to a working solution. For each sub-problem, there will be multiple options with different trade-offs. In practice we move things forward by making informed decisions and committing to a course of action. &lt;/p&gt;

&lt;p&gt;There’s a delicate balance here. Interviewers are assessing your ability to make progress through the design question without getting stuck in paralysis by analysis. But on the other hand, charging ahead with the first solution that comes to mind and not mentioning other potential options is also a red flag.&lt;/p&gt;

&lt;p&gt;Often in real projects you’ll likely need to move forward and make decisions based on limited information. So understanding the trade-offs with a given approach is important. Knowing how the current design can evolve over time to support future requirements that aren't an immediate priority now, is even better. This leads us into the next attribute.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Adaptability&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This attribute touches on both "soft skills" as well as the hard skills in designing systems and frontend components that are adaptable to change. It's your ability to respond to changes in requirements. In practice this relies on two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Not being dogmatic about a particular approach, pattern or technology and being open to discussion. Also feeling comfortable being challenged without getting defensive. It's helpful to view the interview as collaboration between you and the interviewer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Understanding the trade-offs and limitations of a given approach (a common theme in this guide). Without this you are effectively blind to how the system can evolve over time. Understanding the use-cases a particular design supports is important, but for large complex systems, understanding the use-cases it doesn't currently, or can't support is just as important.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For both low level component API design and high level frontend system design, a lot can be said on ways to achieve long term adaptability. This is a topic unto itself (so stay tuned and subscribe to the newsletter for future upcoming posts on this topic 😅).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Operational awareness&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is your ability to consider all the things outside the immediate working solution. It includes things like resiliency, performance, accessibility, testing, observability, security, scale etc. in your design. And calling out any concerns in relation to these as you progress through the interview. These are useful questions to have in the back of your mind so you can proactively call things out as you go along even if you don't end up digging into them in depth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A some questions to keep in your back pocket as you design&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What can possibly go wrong here? &lt;/li&gt;
&lt;li&gt;What are the specific failure modes?&lt;/li&gt;
&lt;li&gt;What does the user experience when things fail? &lt;/li&gt;
&lt;li&gt;What does the user experience when backend is slow ?&lt;/li&gt;
&lt;li&gt;How will we know if things have gone wrong?&lt;/li&gt;
&lt;li&gt;How will we know if what we built is successful? &lt;/li&gt;
&lt;li&gt;How will we handle 100X more data on this screen or component?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can probably think of many more questions whose answers are not without trade offs. There is a lot to consider here. Too much for a one hour interview to go in depth on all of them. But it’s useful to maintain an operational awareness that goes beyond the immediate solution. Your ability to think in terms of the wider context surrounding the solution, and not use just solution itself is a good way to demonstrate your experience as a frontend software engineer. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Product and design sense&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Frontend engineering is a highly collaborative role. We work closely with designers, product managers, and backend engineers. Being able to view a particular problem through the lens of each of these disciplines is empowering. It allows you to view a problem space with many different lenses. And avoids the "if all you have is a hammer, everything looks like a nail" issue highly specialized people tend to have.&lt;/p&gt;

&lt;p&gt;Having general knowledge around UX trends and patterns is also useful too because they also open up different ways of solving a particular problem. It goes a long way if you can proactively identify potentially poor user experiences, bottle-necks, or issues with the UI design.&lt;/p&gt;

&lt;p&gt;The frontend applications we are building end up being expressed in a particular style of common user interface elements. Having knowledge of general UI patterns and their trade-offs from a UX perspective can help you think of different solutions and weigh them up.&lt;/p&gt;

&lt;p&gt;As one simple example, if you’re asked a scaling question, such as how would we handle 1000X of whatever it is we are rendering. One way of thinking about this is to only show what is relevant to the user, and to defer and hide what is not immediately relevant. There are many established patterns you can utilize whenever we can’t fit something into the screen at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some example UX patterns for fitting more stuff into the screen&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;virtualized list&lt;/li&gt;
&lt;li&gt;paginated tables &lt;/li&gt;
&lt;li&gt;tabs &lt;/li&gt;
&lt;li&gt;scroll view&lt;/li&gt;
&lt;li&gt;drawer&lt;/li&gt;
&lt;li&gt;master-detail pattern&lt;/li&gt;
&lt;li&gt;full page navigation&lt;/li&gt;
&lt;li&gt;modal&lt;/li&gt;
&lt;li&gt;combo box&lt;/li&gt;
&lt;li&gt;collapsible section&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The more broad your knowledge is in this area the more likely you can think up of solutions to problems. Or even better, avoid whole classes of problems by taking a different approach to the end user experience if possible. &lt;/p&gt;

&lt;p&gt;In practice this attribute is weighted less in frontend system design interviews compared to the previous ones. But nonetheless one to keep in mind due to the highly collaborative nature of frontend engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to avoid
&lt;/h2&gt;

&lt;p&gt;We now have a good grasp of the different attributes to focus on demonstrating concretely. On the flip side, these are red flags you definitely want to avoid during the interview.&lt;/p&gt;

&lt;p&gt;It’s good to be aware of them so you can course correct if you catch yourself unintentionally exhibiting any of these, either from nervousness from the pressure of the interview or lack of real world experience to draw from if you are going for a junior role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jumping straight into solution mode without probing clarifying questions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is probably the most common mistake candidates make for both coding and system design interviews. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not being collaborative&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind interviewers are assessing your technical communication and collaboration skills in conjunction with your technical proficiency.&lt;/p&gt;

&lt;p&gt;In frontend system design interviews this is weighted more than in coding interviews. The last thing you want to do is be dismissive of a question or challenge from the interviewer without providing technical reasoning.&lt;/p&gt;

&lt;p&gt;While it’s a collaboration, you’re in the drivers seat, and its your job to drive the interview forward towards a reasonable solution. But at the same time you need to be receptive and open to feedback, as they will likely give you helpful hints along the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Being all over the place&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without the right framework on how to approach system design interviews it can be common to jump from one thing to do next in an unstructured way. This is about sticking to one thing at a time and move through each step in a methodical manner. &lt;/p&gt;

&lt;p&gt;Later on in this guide, we’ll go over how to structure your overall approach the the interview, and how to break the interview itself down into sections to work through. And how much time you should be roughly spending on each section. This provides structure to the interview and can help you avoid this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don’t bullshit if you're not sure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes you might simply be stumped by a question. If you don’t have knowledge of a particular area, it's better to be open about your lack of experience, rather than trying to pretend you do. &lt;/p&gt;

&lt;p&gt;If you get caught in this situation, showing vulnerability is better than pretending you have knowledge in an area where you don't. It shows humility, and shows you are open to trusting others opinions which is critical for working in teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not playing to your strengths&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The frontend domain has many sub-specializations within it. You can go incredibly deep on any number of topics. With only around an hour in the interview, you want to try and show case your technical proficiency and knowledge in areas where you are strongest. Unless the interviewer explicitly asked to go deeper into a particular topic it's better to direct the conversation to areas you are most comfortable.&lt;/p&gt;

&lt;p&gt;For example it would be challenging to do a deep dive and talk through the different problems you might face when building a responsive site that also needs to support left-to-right, and right-to-left languages if you’ve never worked on an app that requires mobile responsiveness or internationalization.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to expect in the interview
&lt;/h2&gt;

&lt;p&gt;Now we have a good idea of what interviewers are looking for when running these interviews and what to avoid. Let's switch our attention to the interview itself.&lt;/p&gt;

&lt;p&gt;Most frontend system design interviews will be centered around either a high level design question, like “how would you go about building instagram?”, or a lower level design question like “how would you go about designing and infinitely scrolling newsfeed?”&lt;/p&gt;

&lt;p&gt;Generally the format for a one hour interview will look roughly something like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initial intro and question ~ 5 minutes&lt;/li&gt;
&lt;li&gt;Understanding the problem and requirements gathering ~ 5 minutes&lt;/li&gt;
&lt;li&gt;The design phase ~ 25 minutes&lt;/li&gt;
&lt;li&gt;Optimizations, adaptability, deep dive questions. ~ 20 minutes&lt;/li&gt;
&lt;li&gt;Ad-hoc questions. ~ 5 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you move through the stages of the interview outlined below, you’ll want to be aware of both the amount of time being spent in one section and the concrete design artifacts you need to create for each section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating design artifacts and building momentum
&lt;/h2&gt;

&lt;p&gt;The main tool used in frontend system design interviews is the whiteboard. Utilizing it effectively means you’ll want to be able to show a clear thought process by taking what’s in your head and laying it out in structured sections.&lt;/p&gt;

&lt;p&gt;If the input to a frontend system design interview is a vague question. The output is a clearly defined set of requirements, prioritized use cases, a high level plan, and design on the whiteboard. It's also a shared understanding between you and the interviewer of what the overall solution is along with its pros and cons.&lt;/p&gt;

&lt;p&gt;This ability is an important skill to have when working on a team when designing features. Providing this structure for the team allows others to chime in with ideas, versus just having it in your head. If you’re &lt;a href="https://dev.to/posts/the-three-ds-of-frontend-feature-leading"&gt;a technical lead on a team&lt;/a&gt; providing this space for input is essential.&lt;/p&gt;

&lt;p&gt;At the end of the interview, ideally you could hand over the whiteboard to someone else and they would have a high level understanding of what is required and a good idea the solution explored. Creating design artifacts on the whiteboard as you move through each section of the interview will help you build momentum and confidence throughout the interview.&lt;/p&gt;

&lt;p&gt;Next up we'll dig into what the distinct stages are in the frontend system design interview. Along with what tangible design artifacts we should be focussed on creating in each stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interview format
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;General problem statement and overview&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The initial introduction to the interview usually goes for a few minutes, where you will introduce yourselves and the interviewer will give you your question. &lt;/p&gt;

&lt;p&gt;Your goal here is to listen closely to understand the the problem being presented. The question will either be a high level design question, or a lower level design of a frontend component. It's likely the the question will relate some how to the product the company builds. Or a specific highly used component within the product. So doing some initial practice runs using the company's product you are applying for can help get you in the right frame of mind before the interview.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples of high level design questions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“How would you go about designing a chat application like Slack?”&lt;/li&gt;
&lt;li&gt;“How would you build a photo sharing application like Instagram?” &lt;/li&gt;
&lt;li&gt;“How would you design a rich text editor ?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples of low level design questions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“How would you go about designing an infinite scrolling newsfeed?”&lt;/li&gt;
&lt;li&gt;“How would you design a combobox / typeahead component ?” &lt;/li&gt;
&lt;li&gt;"How would you design a loading progress bar for an API?"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Requirements gathering&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Your goal during this stage of the interview is to explore the problem space with the interviewer. You want to collect a list of functional and non-functional requirements. As you dig for requirements, make sure to listen for any hints. In most cases they will deliberately leave the initial question vague. In other cases they may explicitly tell you what area to focus on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Guidelines for gathering requirements
&lt;/h3&gt;

&lt;p&gt;The best way to get a solid set of requirements is to ask good questions. The best way to generate good questions, is to start with the users of the component or system in mind. &lt;/p&gt;

&lt;p&gt;Thinking about who the users are, how they will access the system and what they will do once they can use the system can help you get started generating many various considerations. &lt;/p&gt;

&lt;p&gt;One way to frame it is if you are tasked with designing this feature of system as part of your job, and  the interviewer is a product manager that you work with.&lt;/p&gt;

&lt;p&gt;Starting with these questions from the end-users perspective will help you find the main use-cases to design for, and can lead you to explore a wide range of factors that will need consideration. &lt;/p&gt;

&lt;p&gt;The example questions below are definitely not exhaustive. But hopefully give you an idea on how asking about the users can lead to identifying what the main functional and non-functional requirements will be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who are the expected users of this system?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example there is a big difference if we expect the users to employees accessing an internal system from company hardware and networks. Versus a anyone with a computer and internet connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How will users access this system?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a broad question that can go down lots of different paths. What devices are they most likely to access it with? This question brings up a wide range of considerations. E.g accessibility, mobile responsive, low power CPU devices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Once they’ve accessed the system, what are they most likely to do?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;A web application may have hundreds of different features available to users. This is a good question that helps prioritize the top two or three use-cases to design for in the next phase of the interview.&lt;/p&gt;

&lt;p&gt;What are the main use cases they will use the system for? Use this question to focus your main efforts into in the interview.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From where are they likely to access it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Brings considerations in networking strategy and offline support. Will they be in different counties? On the subway with spotty connections? How will we handle internationalization?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How will our frontend application be delivered to users?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Seems simple on the surface but there are a number of considerations here. From how the application is deployed - bundling and build pipelines, touching on approaches to different rendering architectures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many expected users are there?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This opens many considerations around scale both for high level design questions and low level component API design.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;How will we know users can successfully use the system as we intend?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Can explore approaches to testing, observability, analytics and error monitoring and resiliency through graceful degradation.&lt;/p&gt;

&lt;p&gt;By just asking simple questions about the user, we have already encountered a large number of considerations. &lt;/p&gt;

&lt;p&gt;There may not be enough time to mention all the considerations in a one hour interview let alone discuss them all in depth. &lt;/p&gt;

&lt;p&gt;So you’ll need to scope things down, proactively call out considerations as they come up, and focus diving deep into particular areas where you are strongest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recapping the requirements gathering phase&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You'll have three main goals in this phase before moving into the design phase:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Define the functional and non-functional requirements. Start asking about the end user. This will likely generate more potential considerations than is possible to cover in the time of the interview.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Define the scope of the interview. Work with the interviewer to identify the top 2-3 core use-cases of the component or system to focus on for the design phase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can also optionally propose a course of action and outline of how you plan to structure the time available in the interview and what you will focus on.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The artifacts you create on the whiteboard in this phase will be a list of requirements. Mostly likely a list of bullet points, with the 2-3 main use-cases highlighted as top focus.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For specific use-cases it might be helpful to phrase them as user stories like “as a user I want to upload a photo and see it displayed on my feed”, or “as a user I want to type my search query into this box and see a list of suggestions”. These act as generic problem statements you can then start to break down.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bullet list of non functional-requirements and considerations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Numbered list on how you plan to approach the interview in stages. Shows your plan of attack and gives the interviewer confidence you can break down an ambiguous question and drive it towards a solution. Also provides a chance to get early feedback on what areas the interviewer may be looking to explore.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  High level architecture design
&lt;/h3&gt;

&lt;p&gt;You'll have the top use-cases of the system or component as you move into this phase of the interview. &lt;/p&gt;

&lt;p&gt;There are no hard and fast rules here. But one way to think about structuring this phase of the interview is taking a top down approach. It can be helpful to start high level from the top with basic wire-frames, and then depending on how you prefer to think about things, work from a top down perspective to flesh out those details, or work from the bottom up.&lt;/p&gt;

&lt;p&gt;Starting with basic wire-frames can help you understand the problem of the particular use-case you are solving and get on the same page as the interviewer. A good approach from there is identifying the abstract data entities that power the specific use-case. &lt;/p&gt;

&lt;p&gt;The design artifacts you’ll create for both high and low level designs typically include but not limited to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High level wire-frame to define the problem and to confirm you on the same page as the interviewer.&lt;/li&gt;
&lt;li&gt;Data entities - e.g pseudo code of type definitions for data entities.&lt;/li&gt;
&lt;li&gt;Break down of wire-frame into individual components, identifying their hierarchy and composition.&lt;/li&gt;
&lt;li&gt;The higher level APIs those components rely on.&lt;/li&gt;
&lt;li&gt;Both user and system initiated events use that interact with the data entities that power the functionality of the use-case.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Guidelines for high level designs
&lt;/h3&gt;

&lt;p&gt;As you being to design solutions to use-cases it’s helpful to be constantly asking - “what is the user trying to do here?” to help frame your solution. Asking this helps prevent creating solutions to problems that aren’t relevant and helps you stay focussed on driving a solution forward that keeps things simple.&lt;/p&gt;

&lt;p&gt;As you practice on real examples it’s good to keep things simple. No need to over-complicate things. The majority of times solutions won’t be ground breaking new designs. A “good enough” solution is one where it solves the specific use-case in front of you, and you have analyzed it’s trade-offs and called out any potential limitations. This is much better than an over-engineered solution that attempts to solve everything at once.&lt;/p&gt;

&lt;p&gt;It can be helpful to apply different lenses to look at the problem from different view points. Especially if you get stuck or not sure how to move things forward. Switching lenses can help you enumerate different potential solutions and considerations that apply to your solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The users viewpoint&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thinking in terms of how the user will actually use the system, and being able to visualize that, can help uncover edge cases and different user experiences you may need to take into consideration.&lt;/p&gt;

&lt;p&gt;For example when navigating away from an infinite scrolling list, when the user comes back would they expect the scroll position to be in the same spot of the list when they went back? &lt;/p&gt;

&lt;p&gt;Thinking about the small details like this from the users first person experience can help uncover implementation details you'd need for a high quality solution. While you are not expected to solve every single problem or consideration that comes up, identifying potential problems ahead of time goes a long way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The architects viewpoint&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the frontend system design interview it's useful to work with your interviewer and make a few assumptions about your users so you know what trade-offs to make as the architect of the system. It's always useful to base things on what users actually do.&lt;/p&gt;

&lt;p&gt;As a real world example, we may implicitly make the assumption users will be using our frontend application in long lived sessions. And so when deciding on a appropriate rendering architecture we land on a client side rendered SPA. Trading off initial page load time for a faster post loading experience.&lt;/p&gt;

&lt;p&gt;But what if that initial assumption isn't correct. For example, perhaps the main entry point into the app is via link in an email notification that something has updated or requires action. And in practice users often do full page loads, and have relatively short lived sessions. In that case you might want to consider optimizing for a faster initial page load based on how the users actually use the system.&lt;/p&gt;

&lt;p&gt;This is a random example, but remember everything is a trade-off. And what is the "right" trade-off will come back to who is using the component or system you are designing and how they actually use it.    &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-exhaustive list of some additional considerations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; - How do we make the app secure for end users? Where are the potential vulnerabilities?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt; - How do we make the system accessible for all types of users?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; - How do we ensure the app is fast? How do keep it fast as more features get added and changed?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery&lt;/strong&gt; - How do we efficiently bundle and send the code down to the user?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability&lt;/strong&gt; - How do we know the application doesn't have critical bugs and can be changed with confidence?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resiliency&lt;/strong&gt; - What are the failure modes? How can we gracefully handle errors when the unexpected happens?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; - How do we know the application is working as expected?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The maintainers viewpoint&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As you design in this phase it's good to proactively call out how things can fail or break, or potentially be a bad user experience.&lt;/p&gt;

&lt;p&gt;For example if you're talking about network communication, you can bring up things like retries, error states, loading skeletons for slow communication etc. How will we know if something is broken? What kinds of monitoring can we put in place to ensure our experience is working correctly and in a performant way? What does fault tolerance mean for this user interface? You don't necessarily have to go dive deep into them unless the interviewer asks, but just mentioning them as you go helps demonstrate your operational awareness. &lt;/p&gt;

&lt;p&gt;For lower level design questions,  this means thinking about how the API can be adapted over time. What happens when we want to reuse, copy or adapt specific aspects of this component with another in a separate part of the app? Does the abstraction allow for that? How easy is the component to adopt and use as a consumer?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recapping the high level architecture phase&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your goal here is design solution for the main use-cases identified in the requirements gathering phase. To recap:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Solve the problem in chunks. With the specific use-cases acting as the entry point for further technical breakdown.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To get started, you can frame the use-case by drawing simple wire frame so you and the interviewer are on the same page.&lt;br&gt;
Before committing to a particular solution you can ask yourself "what is the user trying to do here?" And "what does it look like for this use-case to be solved?" Keep it simple.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identify the underlying entities that powers the use-case.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identify the individual pieces and components that will utilize those entities. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identify the higher level APIs those pieces will need, in order to make the use case possible (in practice typically CRUD interfaces).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are just some guidelines to help you get started. The best way to feel confident about this phase is to go work through real problems you are likely to encounter. If you are interested in seeing this framework applied to real questions, stay tuned by signing up to the newsletter at the end of this guide.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Optimizations, deep dives and adaptability questions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Alrighty, we're in the final stages of the interview now. Let’s finish strong. &lt;/p&gt;

&lt;p&gt;In this phase the interview will usually switch to a phase where the interviewer asks more probing questions. They will typically end up being how you would adapt the design to meet a new set requirements. Either functional, like “how would we go about supporting real time collaboration?” or non-functional, like “how would we go about supporting 100x more data for larger customers?”.&lt;/p&gt;

&lt;p&gt;This is a good chance to demonstrate your technical depth on a particular subject and your adaptability. There's no hard and fast rules here. What you discuss will depend on what the interviewer wants to probe more on. But it's very likely the topic of frontend performance will come up. So you'll want a solid understanding of performance fundamentals if you lack real experience in this area, to ensure you can discus this aspect in depth. &lt;/p&gt;

&lt;p&gt;Here are some example questions you might expect to see in this phase of the interview:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"A customer has raised a support ticket about bad performance and you are tasked to investigate, what do you do ?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Similar to the initial question they will usually be open ended to see how you take an ambiguous problem statement and drive towards a solution. In this case they’re looking for general debugging ability and performance knowledge. Which can roughly be broken down to: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reproducing reliably.&lt;/li&gt;
&lt;li&gt;Narrow down. Identify the type of slowness e.g browser rendering performance or an I/O operation being slow.&lt;/li&gt;
&lt;li&gt;Identifying the root cause.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The principles are the same, narrow down until you find the root cause, enumerate options to solve the issue, identify their pros and cons and make an informed decision on how to move forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"A backend API is slow, however the backend team is flat out and does not have the bandwidth to fix the problem this quarter, what can you do on the frontend to alleviate the pain of this?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Again a good way to explore a problem space is to ask things about the user. Based on this, you could ask things like "How important is it for the user to see the latest data immediately?"&lt;/p&gt;

&lt;p&gt;In this case you wouldn't go wrong to discuss different strategies that can be used to improve perceived performance. High lighting different strategies like pre-fetching upfront at the appropriate time, and various caching strategies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"We now need to support real-time collaboration, what needs to change on the frontend?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes you'll get a question that requires you to adapt your existing design to new requirements. These types of questions will also be pretty open ended. So like we did with the initial question, we'll want to identify the foundational changes required, and focus our attention on those.&lt;/p&gt;

&lt;p&gt;In this case, switching to a real-time system would very likely change both how and what data is sent back and forth between the client and server. So we could focus our attention on the network communication and discuss different potential approaches such as web sockets, server sent events vs long polling and the pros and cons of each as a starting point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"We have a requirement that customers need to support third party plugins - how would the design need to change in order to support this?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Similar to the question above, you'll want to narrow things down to the core challenge posed by this new requirement and solve that first. &lt;/p&gt;

&lt;p&gt;In this case because third party plugins would involve running other peoples untrusted code, so you wouldn't go wrong starting with how to make things secure, and how specific technologies like iframes etc can provide the properties an end solution would need. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To recap this phase of the interview&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this phase of the interview you will be asked some form of optimization question that either deep dives into a particular aspect in more detail. Or how the system can be changed to support new use-cases. Performance is a popular topic in this phase.&lt;/p&gt;

&lt;p&gt;In the case of changes to requirements, for each requirement change, you can suggest multiple methods and enumerate their pros and con, and estimate the impact this change would have on the rest of the system. What else would need to change? What assumptions have now be broken?&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all into practice
&lt;/h2&gt;

&lt;p&gt;Well done if you’ve made it this far. This guide was written to help you think about the frontend system design interview at a higher level. Breaking down what tech companies are actually looking for, and how to structure your approach to the interview. &lt;/p&gt;

&lt;p&gt;If you would like to see examples of applying this framework to a set of real world problems you can sign up to the newsletter Frontend Mastery monthly newsletter. &lt;/p&gt;

</description>
      <category>frontend</category>
      <category>career</category>
      <category>systemdesign</category>
      <category>interview</category>
    </item>
    <item>
      <title>The 3 D's of frontend feature leading</title>
      <dc:creator>rem</dc:creator>
      <pubDate>Thu, 26 May 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/remrem/the-3-ds-of-frontend-feature-leading-58kh</link>
      <guid>https://dev.to/remrem/the-3-ds-of-frontend-feature-leading-58kh</guid>
      <description>&lt;p&gt;Learn the foundational principles of feature leading you can always rely on in the midst of any chaotic project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;A feature lead refers to a person in a team that's responsible for the technical planning and delivery of a new feature or project. It's different to the job title "tech lead".&lt;/p&gt;

&lt;p&gt;It’s common for individual engineers to lead projects at high growth start-ups or big tech companies. It's one of the best ways to level up and gain more experience and seniority. &lt;/p&gt;

&lt;p&gt;Feature leads act as the go to person for everything related to the planning, implementation and delivery of that project or feature. At some point you may find yourself needing to fulfil this role within your team.&lt;/p&gt;

&lt;p&gt;For those completely new to feature leading, it can be a source of confusion and stress. As your primary focus expands from specific coding tasks, to also being responsible for the delivery of a project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So what do those non coding related things involve?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The goal of this post is to try and distill all those fuzzy parts. Some may call "soft skills" into a few fundamental principles.&lt;/p&gt;

&lt;p&gt;These principles come from observations when working with other great feature leads. While trying to understand what it is specifically they do that leads a project to success. Also from my own experience acting as a frontend technical lead on a team. Despite the title, these principles transcend specific domains like the frontend and apply more generally to software projects.&lt;/p&gt;

&lt;p&gt;The aim of this post is to arm up and coming feature leads with a framework to make sense of those fuzzy parts when leading a team to deliver a feature. In practice there is much more to be said on the topic of feature leading that is outside the scope of this post. &lt;/p&gt;

&lt;p&gt;But these are the fundamental principles you can always rely on. Do each of these well, and you’ll have a pretty good time. On flip the side, in my experience, if i've started neglecting one of these, it usually doesn’t end up great.&lt;/p&gt;

&lt;p&gt;So let’s dig in and understand each one in turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1. Discover&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Product managers and designers will typically have a good idea of the value they want to ship to users by the time a project starts. This is where you come in as the team's feature lead. You'll need to discover what needs to be done at a technical level to make that a reality.&lt;/p&gt;

&lt;p&gt;There are three main aspects to the process of discovery:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understanding the problem you are solving. Looking at this another way, what new value will be created when everything shipped to the end user?&lt;/li&gt;
&lt;li&gt;Understanding the current technical terrain.&lt;/li&gt;
&lt;li&gt;Charting a clear path forward for the team on how to get there.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What discovery looks like in practice
&lt;/h3&gt;

&lt;p&gt;What are the concrete tasks a feature lead will be doing when they are in the discovery phase?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generally at the start of a project:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Researching similar prior art from other teams.&lt;/li&gt;
&lt;li&gt;Searching for similar problems that have been solved before. You should avoid re-inventing the wheel if you can.&lt;/li&gt;
&lt;li&gt;Reading through any existing relevant documentation if it exists.&lt;/li&gt;
&lt;li&gt;Speaking with other relevant teams and feature leads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Gathering the high-level details&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discussing your project with relevant subject matter experts.&lt;/li&gt;
&lt;li&gt;Getting the high-level architectural details. And identifying potential dragons before digging in the low-level details is helpful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Gathering the low-level details&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading through existing code and making notes.&lt;/li&gt;
&lt;li&gt;Doing this first without having a picture of the higher level makes it much harder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ball parking high level estimates&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I always try to remember to add extra buffer. This helps to set correct expectations, which is critical at the beginning of a project. It’s better to under promise and over deliver&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The hard parts of discovery
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;No plan survives first contact with the enemy"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the early stages of a project you’ll spend a lot of time in discovery mode. But discovery is also an ongoing process throughout the lifecycle of a project. In practice unknown things will come up half-way and even towards the end of a project. &lt;/p&gt;

&lt;p&gt;This is because in a fast paced environments things change often. Especially requirements. You’ll find yourself cycling back to this phase often, for various sub-streams of the feature. This can make it hard to stick to to detailed plans upfront.&lt;/p&gt;

&lt;p&gt;This phase is about working towards a completed view of the project. On the one hand you need to understand the core parts, so the project can actually begin. On the other, you'll need to be okay moving things forward often with incomplete information.&lt;/p&gt;

&lt;p&gt;You'll also want to understand the things that have the potential to go wrong or slow things down. Both at a technical level, but also at an organizational level. There will likely be many questions that come up during this phase.&lt;br&gt;
For example, "does this feature depend on other teams?" &lt;/p&gt;

&lt;p&gt;"It will be much faster to ship this in the existing tech stack, but we want to move to the new stack. How should we balance this?" &lt;/p&gt;

&lt;p&gt;"The platform doesn't currently support X, do we build something bespoke, wait for support, or build it ourselves into the platform first?"&lt;/p&gt;

&lt;p&gt;In practice some of these questions may be answered easily. While others may be more open ended, leading to further questions and exploration. &lt;/p&gt;

&lt;p&gt;Keep in mind your goal here is understand the current technical reality as much as possible. This happens through asking the right questions and seeking their answers. So it's expected to have a bunch of unanswered questions in this phase. It's a good sign you are starting to develop a clearer picture on what needs to happen. And also what further discovery is needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical tips for discovery
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Validate early&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;After you have an initial idea of your approach, you'll want to confirm you are on the right track. Organizations sometimes have architectural sparring sessions, or dedicated office hours with architects and principle engineers that you can utilize. &lt;/p&gt;

&lt;p&gt;If there is no formal process for this, you can try reaching out to other senior engineers to bounce ideas off. This helps you get early feedback. And also spread awareness across the organization of upcoming changes and features. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical spiking&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;If you’re making a change to an existing system. It’s often super insightful to do a 1-2 day time-boxed spike to get a sense of the actual work involved.&lt;/p&gt;

&lt;p&gt;As you hard code values, mock out data, comment out code and generally leave a trail of destruction on a spike branch. You’ll end up with a useful set of todo tasks like “TODO: need to get the data here somehow”, “TODO: we’ll need to refactor and extend this” etc.&lt;/p&gt;

&lt;p&gt;This is a useful starting point for breaking down concrete tasks that developers can pick up and work on. Translating an idea into code tends to reveal the hidden technical decisions you need to make. That simply having a high level conceptual understanding won't reveal. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Communicating progress&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes in the discovery phase it’s hard to show you are making progress. It can be useful to create tasks in whatever issue tracker you use, dedicated to investigations.  This is both to show progress externally, and to also help keep track of all the investigations that often go on in parallel so you can refer to them later. It's good to time-box these to 1-2 days and provide regular updates on your findings. This helps avoid spending too much time down rabbit holes. &lt;/p&gt;

&lt;h3&gt;
  
  
  Things to watch out for
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Breaking down tasks too early&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes a developer will pick up a task, and discover that the approach outlined is much more difficult than originally thought. Or not currently possible.&lt;br&gt;
This can lead to confusion and increased context switching, and if it happens often can reduce morale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Dunning-Kruger effect&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a kind of cognitive bias we all have that leads to underestimating things. Or put differently, overestimating our ability to do something. We often feel certain about a number of things we don’t actually have direct experience with. &lt;/p&gt;

&lt;p&gt;This is often true when planning work. It can bite you if you have a high level conceptual understanding of something and think “should be pretty straight forward". &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The devil is always in the details:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is often the cause for overly optimistic estimates, which can lead to setting the wrong expectations early on.&lt;/li&gt;
&lt;li&gt;These overly optimistic estimates can also contribute to a sense of time-pressure for developers who pick up stories that have these estimates. Especially in scrum environments, there is an unspoken sense of “the story is estimated as a 1, I will just merge now and do all the extra X in a follow up later”.&lt;/li&gt;
&lt;li&gt;This can lead to a pile of tech debt over time. The thing to watch out here is that there is also a kind of &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Lindy_effect"&gt;Lindy effect&lt;/a&gt;&lt;/strong&gt; for stuffed chucked in the backlog. In my experience the longer something is put off in the backlog, the longer it is likely to be continued to be put off or forgotten about.&lt;/li&gt;
&lt;li&gt;It’s also useful to understand this bias likely exists in external stakeholders. It’s the classic “why does it take 2 weeks to make that button work?”.&lt;/li&gt;
&lt;li&gt;You don’t want to bombard stakeholders with all the technical limitations and challenges. In fact you want to shield them from this to some extent. Being able to communicate the technical landscape in a language external stakeholders can understand is a useful skill to develop on its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having a clear understanding of what the team is building, and how it will built is a critical part of being a feature lead. &lt;br&gt;
As part of this process you are likely to discover multiple possible approaches. Each with their own trade-offs and open ended questions that need to be answered.  As a feature lead you'll want to be continually pushing to keep a forward momentum without getting blocked. &lt;/p&gt;

&lt;p&gt;The next step is taking this context and making decisions that allow you to move things forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. Decide&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This is all about building consensus.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Building products is said to be a series of trade-offs. As a feature lead you are going to be the key person driving these decisions. Along with design, product managers and other teams who may be impacted by your project or feature.&lt;/p&gt;

&lt;p&gt;In an ideal world, everything is discovered, and all decisions around the user experience and implementation details have been decided. As a team, that's a good state to be in.  Assuming solid discovery, and no spanners are thrown in the mix, from there it's a straight forward matter of execution.&lt;/p&gt;

&lt;p&gt;It's unlikely you'll get to this state early on for every aspect of the feature. So you'll want to work towards this state for the various sub-streams.&lt;/p&gt;

&lt;h3&gt;
  
  
  The hard parts of deciding
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Things that matter most should never be at the mercy of things that matter least."&lt;br&gt;
"Deciding what not to do is as important as deciding what to do&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is a bit of a balancing act here.&lt;/p&gt;

&lt;p&gt;While you want to have clarity on what needs to happen, deciding too early means you could be building the wrong thing, the wrong way. This can result in your decisions being undone down the road, leading to re-work and loss of team morale and momentum. &lt;/p&gt;

&lt;p&gt;This can be especially painful if you decide on a course of action that effects other teams unknowingly.&lt;/p&gt;

&lt;p&gt;Often times it is best to defer making engineering decisions until all information is available, if it ever comes at all. There really is no silver bullet here. The best you can do is break things down and start building consensus on the smaller independent sub-streams. So you can get started, while more information is then gathered.&lt;/p&gt;

&lt;p&gt;Often times what is proposed is not workable in the time frames given. Speed, quality and scope - you can usually pick 2. In the majority of cases picking speed and quality is the right move. This follows the principle that it is generally better to do less work to a high standard, rather than to try and do everything at once poorly.  At a higher level this maps to the lean way of incremental delivery.&lt;/p&gt;

&lt;p&gt;Sometimes you'll find yourself between two or more competing interests across teams. It’ll be your job as the feature lead to offer multiple solutions or ways forward. Ideally with high level estimates, that balance many sides. And then to ensure you’re driving those decisions to an outcome one way or another.&lt;/p&gt;

&lt;p&gt;Your ability to come up with a good set of options comes back to knowing the why behind the feature, and understanding the customer pain you are solving.  Knowing this allows you to focus on what matters most.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What driving decisions looks like in practice&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Creating, updating and driving &lt;a href="https://www.atlassian.com/team-playbook/plays/daci"&gt;decision register&lt;/a&gt; documents. These are documents that capture the problem statement, propose a bunch of solutions and provide a format for building consensus in writing.&lt;/li&gt;
&lt;li&gt;Collaborating with product to understand what is in scope and by when.&lt;/li&gt;
&lt;li&gt;Updating and driving smaller, less impact decisions and updating decision registers.&lt;/li&gt;
&lt;li&gt;Talking with other teams and people to engage in the decision making process and garner alignment.&lt;/li&gt;
&lt;li&gt;Communicating the outcome of your decisions via whatever status reporting format your team uses, weekly syncs, stand-ups, async posts etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Practical tips&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Run a project kick-off&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://www.atlassian.com/team-playbook/plays/project-kickoff"&gt;project kick-off&lt;/a&gt; is used to build initial consensus on what a successful outcome looks like. These are helpful for aligning everyone involved on the value you plan to ship and roughly by when. &lt;/p&gt;

&lt;p&gt;It's useful to get everyone on the same page early on in this regard. Because as you make trade-offs down the line, you can optimize and generate solutions based on what the customer truly cares about in the feature being delivered. Establishing what that is as a group before work begins is very useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep track of your decisions and the reasons why they were made&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;It’s useful to record each decision and why it was made. This helps you own the decisions you make and move forward with confidence. People will question some decisions (not necessarily maliciously but out of curiosity). Having a decision register helps you come back to the why as time passes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quantify decisions if you can&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By passing through the discovery phase, it's likely you would have encountered some issues that need extra work to be done. There will come a point where you will need to convince others that some action needs to be made, or avoided.&lt;/p&gt;

&lt;p&gt;It's helpful to articulate the value something will provide whenever you put forward a proposal. Or alternatively the issues you won't have to deal with down the line if you do the work now.  Strive to get metrics if you can. For example refactoring something to make it faster. How much faster? by what %, or correlate it to existing support tickets if available. Sometimes even having a ballpark estimate is useful for making a stronger case.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Things to watch out for&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Not being explicit about the trade offs you're making&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At some point you will need to decide between a trade off. When recording decisions it’s useful to be explicit about the trade off you are making. This serves as both a way to leave a paper trail for your work and serve as documentation for the future.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decisions leading to rapid accumulation of tech debt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Depending on the project you’ll likely find situations where you simply have to cut-corners and introduce tech-debt due to time constraints. Avoid this if you can, but in my experience it’s quite hard to completely avoid.&lt;/p&gt;

&lt;p&gt;In these situations it's useful to make this clear to external people "we are borrowing time from the future" so we can provide customer value faster, but at some point this will come back to bite us. &lt;/p&gt;

&lt;p&gt;Own this as your responsibility as the person closest to the implementation. Communicate this back out to the team and relevant stakeholders, and ideally get commitment to cleaning things up in a following iteration if possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Having your decisions undone&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This leads to re-work and inefficiency. Also loss of momentum and morale.&lt;br&gt;
In practice usually this will come from not assessing the impact on other teams and other dependencies. Or from not understanding the current technical landscape in detail enough, and having to back-track out of a particular approach. When locking in decisions make sure to include every relevant party. Good discovery is key in avoiding this situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. Delegate&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This is all about not trying to play what is a team sport, solo.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As you discover what needs to be done and decide how you will do it, as feature lead you'll need to delegate. As you discover and decide, you’ll need to regularly download what's in your brain to the rest of the team. &lt;/p&gt;

&lt;p&gt;Delegation is not about palming off work to others. It comes back again to consensus building. It's also about creating a sense of ownership in the rest of your team. And as much as possible having each member &lt;a href="https://www.investopedia.com/terms/p/principal-agent-problem.asp"&gt;"act as a principle"&lt;/a&gt; by having clear areas of responsibility. You want to eliminate as many single points of failure in the team as possible - aka the &lt;a href="https://en.wikipedia.org/wiki/Bus_factor"&gt;"bus factor"&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  The hard parts of delegating
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to go fast, go alone. If you want to go far, go together.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes you might find that there aren't enough people to delegate to, depending on your team size and team composition. In these cases it’s best to speak to your manager or project sponsor to get either get more help, or reduce scope / increase the timeline.&lt;/p&gt;

&lt;p&gt;Often times it will be faster for you to "just do it", rather than share the context and delegate to someone else. There is a trade-off to balance here, in the majority of cases it's better to share whatever is in your brain with the team and delegate as needed. &lt;/p&gt;

&lt;p&gt;If you're new to feature leading it can sometimes be difficult to feel comfortable delegating to others. This is a pattern to identify and break as early as possible to avoid piling everything on yourself and burning out.&lt;/p&gt;

&lt;h3&gt;
  
  
  What delegating looks like in practice
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Breaking down tasks and fleshing them out with all the necessary information&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This also means delegating to your future self. Providing all the context needed and links to files in source code where you need to make changes. As with all the context switching it's so easy to lose or forget context, where you need to make the changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running estimation meetings&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Or any meeting where the engineers sync up to talk about the work they are going to do. This involves walking other engineers through tasks to provide technical context. So the others are on the same page and are in a position to work on them independently. It also includes regularly organizing the backlog based on priorities. And being available for ad-hoc meetings and answering questions for those taking on the work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical tips for delegating
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Have developers work and own small subsets of the feature&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;This makes development more efficient and creates a sense of ownership. Pieces are ideally small enough to not create knowledge silos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delegation can apply to more areas than coding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s not just about delegating the development work. For larger projects you’ll likely need to delegate the work related to discovery. This also applies to driving decisions to a decided state when multiple decisions are needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Things to watch out for
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Trying to take everything on yourself&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You have all the context and it often just feels like it will be quicker and easier to do it yourself. Most likely you'll just put yourself on the path towards burn out, it's also inefficient to try and do everything yourself. Remember it's a team sport.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not shielding the team&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ideally when you download your brain to the team, it is with information that won’t later become irrelevant.  As you go through the process of discovering and driving decisions, there may be a churn in technical direction. &lt;/p&gt;

&lt;p&gt;It’s useful to be mindful of this as you share context with the team. You’ll want to strike a balance between shielding them from that versus bombarding them with details at every step of the way, which usually leads to confusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over delegating&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes over-delegation can happen. It’s much rarer compared to feature leads piling everything on top of themselves, but something to watch out for none the less. &lt;/p&gt;

&lt;h2&gt;
  
  
  The most important D
&lt;/h2&gt;

&lt;p&gt;Chances are at some point you will be called upon to lead a project as a technical feature lead. To take what is just an idea, and &lt;strong&gt;drive&lt;/strong&gt; that through to something concrete that provides value for other people. &lt;/p&gt;

&lt;p&gt;When this happens, don't panic! &lt;/p&gt;

&lt;p&gt;Remember the 3 Ds and you'll do fine.&lt;/p&gt;

&lt;p&gt;Good luck.&lt;/p&gt;

</description>
      <category>career</category>
      <category>frontend</category>
      <category>webdev</category>
      <category>agile</category>
    </item>
  </channel>
</rss>
