Don't Solve Problems, Eliminate Them!
In this talk, Kent C. Dodds discusses how adopting the philosophy of "Don't solve problems, eliminate them!" can lead to better long-term outcomes.
The concept of the Problem Tree is introduced as a way to think through a problem and its potential approaches to finding solutions (and more potential problems along the way).
Through "real life" examples like electric vehicles eliminating emissions to better air quality as well as code-specific examples including the role of React Hooks in evolving how components are built, Kent makes the case for shifting your problem-solving approach.
Share this talk
Transcript
Hey there, Epic Web Devs. My name is Ken C. Dodds and I am so excited to talk with you about something that I think is really important for both programming but also life, and that is don't solve problems, eliminate them. So, before we get started, I always like to start my talks off with a little bit of brain juice. So if you haven't moved
in a little while, I encourage you to stand up, stretch, do some air squats. Normally when I give talks I have everybody stand up and get their bodies moving. Your brain needs blood flow to be able to process at peak efficiency and that's what I want for you. So if you haven't moved for a little while, I want you to get up and get moving. Pause the
video. I'll be here when you get back. Okay, you all set? Let's go. So this talk is about problems, solutions, and trade-offs. I want to convince you that solving problems is great, eliminating them is better, and avoiding them is even better than that when it makes sense. This talk is not a bunch of
code examples, there are some. It's not 100% about code. It's not really even domain specific. So, we're going to be talking about a lot of things. So, this is what we're going to be talking about, the problem tree. You're not supposed to be able to see it very clearly. I made a simpler version because
the real problem trees are enormous. And they can apply to everything in life. So here's the simpler version, we have one core objective and there's a problem that we're trying to solve, that's that's the core objective and that that line connecting these two nodes is a problem. Each one of these
nodes it's a solution and then the line is the problem that stems off of that solution and then we have multiple solutions for that. Just goes on forever. So you solve one problem and that solution creates more problems that you then need to solve or you just decide you don't solve them at all. So we're gonna be referencing this quite a
bit. So you are a problem solver. I think that's a pretty reasonable assumption to make. This is why we're the dominant species on the planet, is that we're constantly solving problems to make the lives of ourselves and those that we love better. Unfortunately, this also comes with it, the fact
that we're problem seekers. We're always curious, looking for new things that we can solve, and solving problems is a lot of fun. And so we often will look for problems to solve and this can be a bit of a problem in itself. So when the pandemic hit in
2020, my sister who's a accomplished violinist noticed that all of her friends were struggling to get gigs to play the violin. And I'm not gonna go into too much detail, but she wanted me to help her build an app. How many times have we heard that before from our friends and family? Like, could
you just build me this app? And she wanted to build an app that would help solve the problem. Effectively, what the app entailed was to take all the features of Zoom, Google Calendar, and Tito and build them into a custom piece of software. And I tried to convince her that she should really just
use these different pieces of software manually integrating those things so that she could just see what the problems really were because I'm convinced that we're much more effective at solving the problem when we know what the problem is. And so it's just so often we build solutions
before we understand the problem. And so I just convinced her hey you don't spend a bunch of money building some custom software that where we actually can just take a couple pieces of different software and manually integrate them together. And it turned out she didn't actually build the custom software. She didn't actually
work on the problem at all. She decided it wasn't worth her time. And I'm really grateful for that. She did a lot of market research and stuff and she had money and she was ready to spend it and I'm glad that she didn't because it didn't end up being anything she wanted to do anyway. But this is one principle that I want to take away from this And it
is it's better to avoid problems than to solve them. Elon Musk was asked in an interview about this and he said possibly the most common error of a smart engineer is to optimize the thing that should not exist. And I kind of thought he was talking about me for a second there because I often end up just so in the weeds of the thing that I'm
trying to solve that I later realize doesn't need to exist at all and so when when you're looking at a problem tree just in your head think about like maybe we get down to this solution right here and that off of that stems a ton of problems or maybe
this problem right here involved a really complex solution. When you're working on that solution if you're thinking man if I'd known that this would be a problem all the way back here, I never would have started this project to begin with. And when you're kind of thinking that, maybe the solution
is actually just to leave that thing unsolved. Maybe this is a feature that only two or three users use and even if you ended up losing their business, it actually wouldn't be a huge problem for the business as a whole. So, like, focusing your efforts on the things that give us the most benefit
are where you should be spending your time. And so don't get so excited solving problems that you forget that the problem needs to be worth solving. That is the idea of problem avoidance. Now, sometimes you can't avoid problems. Sometimes they're just such a big problem you can't just let
the house fall off of the whatever that is the ledge there. So for those sorts of things you should be looking for ways to eliminate the problem. How can you get rid of that problem? So instead of just going straight for, alright how do we solve this? See if you can avoid getting your house on the ledge
to begin with or eliminating the problem. Let me give you an example or first I want to say that solutions hold you captive to their maintenance and so this is why we want to eliminate the problem and just try to avoid solutions to begin with because as soon as you have a solution you have to
maintain that solution in the long term so if we can maybe eliminate the problem all together and not have to solve it then that's a much better situation. So here's an example in the real world. Problems that have been solved for personal mobility. So we decided hey let's build a car and
that way we can go really really far and and not have to like join up in a bus and travel across the country that way we just get in our own car and we can make that distance but the car solution has a lot of problems attached to it especially the internal combustion engine that we've had for hundreds of
years. So the exhaust is one major problem. It's very dangerous for people to breathe exhaust, and so we've developed these tailpipes and all of that to kick the exhaust out the back of the car instead of in the cabin. Unfortunately, it does a lot of harm back there too, but at least it's not harming ourselves, I guess. Stopping is also a major
problem. We are going pretty fast and so we invented disc brakes and all that, but Yeah, there's a bit of a problem there that we just decided not to solve because those disc brakes do need to be replaced, they don't just disappear, that actually goes into the air and we're all breathing that, but you know, I guess that's not a problem we're going to solve. But stomping was
solved, so that's good. Car fires, you are literally making miniature explosions at like a very fast rate, right, inches from your feet essentially, so yeah, car fires are definitely a problem that a lot of complex solutions have been built around to make it less likely for cars to
catch fire due to the way that combustion works. And then unfortunately sustainability is one problem that while there have been like some advancements to make cars more efficient and stuff it's just impossible to make internal combustion engines so efficient that they are sustainable it just will not happen. So these are
some problems that have been worked on and sort of solved but they're not really solved. The only way we can actually solve this is by completely going back up the problem tree and completely changing the original solution. This is a engine from a Tesla Plaid Model S and the exhaust problem has
been eliminated. That's just not a problem. There is no tailpipe on electric vehicles. The stopping problem also drastically reduced because you can convert kinetic energy into electricity by just like flipping the way that this works where it will now charge the battery and that helps slow down the
car. So while you absolutely do need disc brakes, you don't use them nearly as much because you can convert that kinetic energy into electricity and slow down the car that way. So it's actually very common to not have to replace your disc brakes on an electric vehicle for this reason because you just
don't use them as much. So not completely eliminated but drastically reduced. Car fires also of course everything can catch fire ultimately but you're 11 times less likely to have your Tesla Model 3 catch fire than any other vehicle, combustion vehicle, which kind of makes sense because combustion you're literally
setting things on fire, whereas that's not the case with an electric vehicle. And then sustainability, 100% you can charge your electric vehicle by the sun's solar rays and drive this car all over the place. And then the battery can be fully recycled and used again
indefinitely. So the sustainability problem has been solved with these electric vehicles, which is really great, and this is all because we eliminated problems. We're eliminating the entire problem tree. So let's say we're down here,
we're working on the sustainability solutions and stuff like that, and we're like, man, this is a really hard problem. So when you get stuck like that, I want you to go up the problem tree and think, is there a different direction we could have taken back up here to completely change the the outcome
and the different problems that we have to deal with and we can end up with a different solution that leads to much smaller problems. That is problem elimination. And actually this is a really great example of problem elimination is Tesla. Their giga casting is one good example, the
way that they manufacture battery cells, a lot of really interesting case studies from the elimination and innovation that's going on with Tesla. So something interesting to look into if you're interested. Problem elimination in software. Let's talk about something a little closer to home, something that we're all working
with every day. So React and code reuse. So years ago, those of you, feel free to raise your hand at home, those of you who have been working with React for a long time. I started with React in 2015. We used to use React Create Class, then we had class
components, and in both cases we had these three different pieces of a React component that allowed us to control when different pieces of code ran. So we had component to mount, component to update, and component will unmount. And with
those we could stick in the code for subscribing to a server-sent event, and updating our subscription, and then unsubscribing at the end, as an example. And the problem came when we wanted to reuse the code, because if I had this really
great way for subscribing to server-sent events or for updating that document based on unread counts or whatever, I had to spread that code across these three different boundaries and so I could take each one of those and put those in functions and just say call each one of these functions. Not a great experience or you know we developed different patterns to solve this
problem. So there's the higher order component pattern and there was the render prop pattern and each one of these came with their own sets of problems. So higher order components, really difficult to make work nicely with TypeScript. And you had prop clash problems. And the render props were actually pretty good, except you would
end up with this really deep pyramid. And the order in which you render things didn't actually matter, but it kind of looked like it did. And so, yeah, things were not super great, but they were okay. And then the React team was looking at this problem and various others and thinking, you know what, what if we go up the problem tree a little bit
and completely change the approach and we create hooks and so instead of having these components with these life cycles we have these hooks that are responsible for different pieces and so now instead of having these different patterns the pattern is actually just functions it's just JavaScript you You have this little code that you want to reuse, you put it in a function, now we call
that a custom hook because it's using a hook. And so what's really cool about this is we can kind of co-locate all the pieces, the concerns of the specific thing that we're trying to work on and and now we don't have to worry about the reusability aspect and hooks just made things way more
reusable. As evidence of that, I had a workshop about React patterns and that workshop drastically reduced in size because there were just so, we didn't have the problems that those patterns solved anymore. So, huge, huge benefit to bringing hooks. Now, of course, anytime you eliminate a problem by like taking a different
direction on this problem tree, you do have additional problems. But the point is that the problems that you have are smaller. They're fewer, they're much better to deal with than what we had before. So, what got me thinking about problem elimination is Remix. I worked at Remix
for a little while but I actually I loved Remix before I joined the company and I've been using Remix for years. I love this framework and I want to talk about some of the problems that were eliminated for me when I started using Remix. So first of all, I started using Remix by rebuilding
my website and I want to just give you an idea of what
things were like when I initially released this site it was 27, 000 lines of code, I worked with a bunch of people to help work on this and I did most of the the back-end work, I had a lot of help with some of the front-end stuff, so it's not like a simple
site and it just really changed the way that I felt about building for the web because I used Remix. So let's talk a little bit about a couple of the things that Remix did that helped eliminate a lot of problems for building web apps for me. So nested routing is one of them, we'll look at an example of
how that made things better but nested routing is such a good idea that everybody else is doing it also Remix did not invent nested routing but its application of nested routing really changed a lot of the thinking in the industry, so we'll look at why. Seamless clients and server, this is something that definitely a lot of frameworks are
starting to adopt as well and we'll look at why that's so great. The foundation on the web, this is something that Remix nailed and it has a lot of really awesome implications that we'll look at. Mutations are so, so simple and all based on top of
the web platform also, so we'll look at that a little bit, but basically the idea is you just build it like you're building an old style multi-page app and Remix kind of emulates the browser behavior to give an excellent user experience but also a situation where you actually don't have to think about data mutations
or keeping your data up to date on the page once mutations happen. It's really, really great. And then, its CSS story is also really, really awesome, so we'll look at that a little bit. So, let's start with the client and server, the seamlessness here. So, Remix makes it feel like
you have no giant chasm between the client and the server. It feels like it's all together. Typically, you're like shooting grappling hooks over this giant chasm or canyon. And with Remix, it feels like you've got this solid bridge that you don't have to think about and it's really really
great. So as just a quick example of this, my signup page on my website allows you to choose a team that all of your blog post reads will be associated to and then it's like this contest between these teams. And one thing I wanted to make sure of was that I don't give any preferential treatment to
any team and so every single time I land on the page I want it to be different. But another constraint that I had was I didn't want to have any loading spinners anywhere. I wanted to have as fast an experience as possible, server rendered everything. And so no, like, I couldn't do this at build time, it had to be during the server render.
And Remix makes this really really easy. I have this loader function where I can take the teams and shuffle their order and that happens on the server and then in the server render the React component is going to be rendered here and it just grabs the data from the loader and then it I can map out those
Cody koalas in in that UI and in the order that they came from the loader. And so this is one of those really simple things that used to be a lot harder for me and Remix eliminated that problem for me by making this server client communication just so
seamless. Another example is I have this podcast page where I'm listing a bunch of different seasons and the episodes on those seasons and I just want you to look at a couple of the cool things that we've got in here. So we'll talk about this request object and the response object that comes back from this JSON. That's just like web platform
stuff. But also, if there's not a season, I throw a response. So web response object. And that will end up in my error boundary which Remix will display for me in this specific area of interest
and so this the the simplicity of having my server code be right next to the UI code that needs that server code just makes this really straightforward and on top of that having it be a separate thing not within the component actually
makes it a lot easier to maintain as well. So eliminating that problem of that server client communication by just making it super seamless by putting that code together. It's great. And yeah so now I've got a really nice error page. Here's a 404 example page. So yeah it's a really awesome
experience. So let's talk about using the platform, the web foundation. This is another thing that I saw early on with Remix that I thought they just really nailed. So first off, a guiding principle for Remix is progressive enhancement, and that's a big reason why they just use the web
platform for so much stuff. So everything is based off of the WebFetch API request and response, you're dealing with those objects directly, so you don't have the problem of needing a bunch of documentation for your own way that you interact with the web platform. Remix actually just kind of
wraps things like Express or Fastify or like all these different frameworks and turns those into WebFetch request response objects. And so wherever your code is, whether you're deploying to CloudFlare workers or to a Node environment or Deno, wherever it is, it's always going to be
request response. The mutations API we talked about a little bit, it's all just forms and that makes it really straightforward and declarative for building nice user experiences that progressively enhance, that work before the JavaScript shows up. The Remix also uses the
platform to make it so that native ESM doesn't have the cascading import problem but it just pre loads all of the stuff for you so you don't have to worry about things being slow but you can still use the latest technologies and it also, because Remix is your router and your data fetching library, it's
able to know all of the data requirements and code requirements and CSS requirements and whatever else you want before actually even having to render anything and so by virtue of this it's able to prefetch as the user's navigating around really efficiently. So as you're navigating
around my website, you'll see a bunch of prefetched data and code and all of that and then when you finally come around to actually clicking on that link everything is in the browser cache and so it's all web platform stuff. What's really cool about this too is that if you are like, oh, I want to click on this link and then you say oh, never mind
I'm not going to. All of that stuff was preloaded in the cache, so if you close the tab and then come back and then you do click on it, it's already in the cache. Whereas with what we typically do with this is we'll just stick things in an in-memory cache like with react query or something and if the user closes the tab that thing's gone but by leveraging
the web platform we're able to just use the the browser cache for this stuff which I think is really powerful. So let's first off I just want to say CSS just used tailwind, it's phenomenal, I love it and I think that it it's been really helpful for me, I use it on KentCdots.com, it's just great.
However, if you just want to write some regular CSS then Remix has the ability to have your routes specify the CSS that they actually care about and so in each one of your modules you have this links
export that you can say here are the link tags that I want on the page when this route is active and when this route is not active then those get booted and so you don't have to worry about having CSS that's on the page when it's not supposed to be. So typically when we're writing regular CSS files at
big companies that I was at before Remix, I was just concatenating all the CSS files and sticking it on the page all at once and and that worked and it was fine. However, it was actually not fine at all because I had to always worry about the impact that my CSS file changes were having to the entire app.
Whereas with this model all I have to do is look at the route modules that are using my CSS file and as long as those routes look good then I don't need to worry about it. And in practice it was typically just one route anyway, so I could make all kinds of changes to that CSS file and as long as it looks good I didn't need to worry about clashes. So Remix
didn't eliminate the fear of clashes necessarily, like you can have a clash in a single file. What it did was it made those clashes predictable and that is really powerful. So I wanna talk a little bit about simple mutations in nested routing. So here we've got a typical like
sort of user dashboard sort of thing. So we've got the top nav and you've got a couple options up there, you've got maybe the user profile on the top right and the logo on the top left, and then you have a list of items, let's say that these are users of your your software, and you can edit those users
and that's what we're looking at right now. So pretty typical thing you can maybe delete the user and you can save the user after making some changes and we're selected on this user right now. So the way that this works is you have your route that is responsible for everything from open HTML to closing HTML. Then you have your users route so
here we're clicked on the the users link and so now this area below is showing what we want to have active when the users is clicked. And then we have the user ID, so this is the specific user. Each one of these sections is its own component, so the root
component is responsible for rendering this piece, the users component is responsible for rendering this piece and then the user ID is responsible for rendering this piece. And then for each one of these that's apparent they say well here's what I render and then any of my children are just going to go here that renders an outlet
component. So this nested routing has a lot of really cool implications that I want to talk about. So first of all, loaders run in parallel. So if I navigate around to these different pages, maybe I click on this one and that's going to take me to a bunch of other sub routes. Each one of those
loaders is all, they're all going to run at the exact same time and so I get really optimal data loading performance in that way so we don't have this waterfall or this cascading waterfall effect which is really great. Remix only loads what's needed so if I change from this user to this one, then I don't have to worry about reloading the
data up here because that data didn't change, we're just getting new data and so we're just getting the data for right here. We don't have to get this data again either. And it sounds like, yeah, of course you don't, but in practice, without Remix, that was actually a relatively difficult thing to do, and Remix and React Router
version 6.4, that brought a lot of Remix features into React Router, makes doing this very trivial. Mutations, trigger, and validation of all the loaders. So if I change this, maybe my user profile, and I hit save, now my user profile picture up here needs to change relative to this
one, right? And so because Remix is just emulating the browser, if we were to do this without JavaScript, then what would happen is you would hit save, the browser would do a full page refresh, which would trigger regeneration of all the HTML, including getting a fresh version of the user's
avatar. So Remix does the exact same thing. It just reloads data rather than refreshing the entire page, making a much better user experience. And so what that means is when you make a mutation, you don't have to worry about getting all of the data up to date because it is just emulating what the browser would have done and you wouldn't have had to worry about that then either. That's a huge,
huge benefit to nested routing and to emulating the browser by invalidating loaders when you do a mutation. That's a lot of what comes from a framework that is fully integrated and manages mutations and data loading for you. So then
context for shared UI state and remix for shared server state. So there's actually not a lot of state that you need to manage when you're working in a Remix app. So like if you need to share some state between the parents and the children and stuff you just use regular context. I've built a
number of apps in Remix and I actually have never really had to do that at all. However, you totally can, no problem with it in the couple of situations where it can be useful. But Remix is managing your state from your server so you don't have to think about it. It's pretty great. And there's no layout
component, so if you're familiar with Gatsby or Next with their pages directory, you have to have like to have reusability all over the place, you have to have this layout component and so that like you have the same top nav and left nav and all of that stuff. You don't have to worry about that because
each one of these segments of the URL corresponds to this part of the route and so you get that sharing automatically which is really nice And that was a huge benefit to me when I was migrating stuff over to Remix. So, Remix does a lot of things and it's just one of the examples of using problem
elimination as a mechanism for drastically simplifying the problems that you have to solve and the solutions that you use to solve them. So a lot of you might be thinking, yeah okay Kent this problem elimination thing that sounds just like trade-offs and stuff, yeah that's exactly what it is, yeah. The idea is that we want to eliminate big
problems in exchange for smaller ones. So we're not actually getting rid of problems altogether necessarily. We do have trade-offs, but the idea is we want to reduce the size of the problems that we have by taking a different direction. So in conclusion, solving problems is great. Keep
solving problems, please. But if you can, try to eliminate those problems so that you don't have to solve them or that the problems that you're solving are much simpler. And in some cases, maybe avoid the problem altogether. Maybe that problem doesn't need to be solved, doesn't even deserve
the time of your life to solve. And so, yeah, in general, if you can't avoid the problem, try to eliminate it by changing your approach, and only if that fails, then you solve the problem. Don't just get distracted by solving problems. Let's make the world better. Thank you so much. Have a good one. Bye!