← All Talks

Don't Solve Problems, Eliminate Them!

Kent C. Dodds
Kent C. Dodds

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.

Transcript

00:00 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

00:19 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

00:37 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

00:56 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

01:12 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

01:31 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

01:48 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

02:08 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

02:24 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

02:43 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

03:03 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

03:22 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

03:41 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

04:01 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

04:21 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

04:40 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

05:00 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

05:18 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

05:36 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

05:53 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

06:13 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

06:32 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

06:49 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

07:09 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

07:28 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

07:48 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

08:09 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

08:29 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

08:48 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

09:07 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

09:26 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

09:45 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,

10:03 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

10:21 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

10:40 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

10:57 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

11:14 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

11:31 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

11:48 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

12:08 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

12:28 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

12:47 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

13:06 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

13:24 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

13:46 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

14:03 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

14:21 my website and I want to just give you an idea of what

16:25 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

16:44 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

17:02 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

17:22 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

17:40 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

18:00 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

18:18 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

18:34 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

18:53 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.

19:12 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

19:30 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

19:49 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

20:09 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

20:24 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

20:45 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

21:04 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

21:23 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

21:40 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

22:00 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

22:18 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

22:37 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

22:56 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

23:16 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

23:36 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.

23:57 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

24:15 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

24:33 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.

24:51 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

25:11 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

25:29 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

25:47 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

26:07 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

26:24 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

26:42 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

27:01 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

27:21 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

27:40 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

27:59 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

28:17 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,

28:37 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

28:56 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

29:15 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

29:35 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

29:53 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

30:12 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

30:31 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

30:50 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

31:07 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!