Red-Green-Code

Deliberate practice techniques for software developers

  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter

LeetCode Tip 43: Find the Zone of Optimal Improvement

By Duncan Smith Leave a Comment Nov 1 0

LeetCode 2023

To practice LeetCode effectively, you have to choose the correct problem difficulty. If you start with Hard problems when you’re first learning a topic, you won’t learn much because you won’t be able to make much progress on the solution, so you won’t get a chance to practice the topic. At the other extreme, if you practice Easy problems when you already know a topic well, you won’t learn much because you won’t challenge yourself.

But problem difficulty is not the only way to adjust the difficulty of your practice. You can also use other aspects of your practice routine to find the optimal difficulty level.

To learn more about the ideas in this tip, see Desirable Difficulties: When Harder is Better for Learning by Scott Young.

Desirable difficulty is the idea that making learning harder can make your study process more effective. For example, switching from Easy to Medium problems for a topic that you have some experience with will make it more difficult to solve each problem. But the additional challenge provided by Medium problems will push you to learn more about each topic than you would by solving Easy problems.

Techniques

Once you select a problem with the correct difficulty level, you can further find-tune the level of practice difficulty using these techniques:

Spacing

If you practice a problem until you solve it, then delete your code and immediately write it again from scratch, it will be easier to solve the second time. Since your first attempt was just a few minutes earlier, you’ll easily recall some details. If instead you wait a week to make your second attempt, you’ll find it more difficult to come up with the solution. But in the long run, you’ll make more progress if you space out your practice. This is the idea behind spaced repetition.

Variability

If you practice a sequence of binary search problems at a similar difficulty level, the problems will get easier over time. Each problem will give you more experience with binary search, and you’ll be able to concentrate on the solution patterns required for binary search, with no distractions from other problem types. If instead you alternate between binary search problems and binary search tree problems, it will make your practice more difficult. When you solve a binary search tree problem, you’ll displace a bit of what you remember from binary search, and vice versa. It will take longer to become proficient at both topics than it would if you learned them sequentially. But in the long run, interleaving topics will help you gain a stronger mastery of each topic.

Testing

Reading someone else’s solution is much less effective for learning a topic than solving a problem on your own, even if you are reproducing a model solution. The process of implementing a solution gives you experience that you can’t get just by studying code. This is known as the testing effect.

Research

Although research supports the effectiveness of spacing, variability, and testing, there isn’t a consensus about why these techniques work. One theory is that the brain maintains a retrieval strength for each memory. You can recall memories (like how to implement binary search) more easily when the connection to that memory is stronger. The brain strengthens the connection when it detects that a stronger connection is needed. If you retrieve a memory easily because you used it recently to solve a problem (low spacing), because you have been practicing similar problems (low variability), or because you are just reading the solution (no testing), your brain concludes that the connection strength is fine. On the other hand, if you have to struggle to recall a key concept but you eventually retrieve it, that’s a signal that the concept is important, so the brain strengthens the connection.

There’s another theory explaining why variability is useful for learning. When you interleave similar problems, your brain has to distinguish between them. For example, consider binary search problems and binary search tree problems. In both cases, you need to find a target element by moving left or right in an ordered data structure, eliminating half the search space on each iteration. But for binary search, you’re using an array, so you can jump to any location in constant time, while for the BST, you are traversing tree nodes one at a time as you search for the target node. Solving a binary search tree problem after having recent experience with binary search forces you to think more carefully about which techniques to apply at each point in the algorithm, and which algorithm is most useful for the problem.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 42: Getting Past a Learning Plateau

By Duncan Smith Leave a Comment Oct 25 0

LeetCode 2023

When you first start using LeetCode, progress can be rapid. If you have programming experience but no LeetCode experience, you can get a general idea about this type of programming by solving a few easy problems. Similarly, when you first learn about a topic like binary search, it might only take a few hours before you can solve straightforward problems on your own. But then progress gets slower. Medium problems are trickier and take longer to master. To solve problems at that level, you have to understand the target algorithm well enough to customize it for each problem. And Hard problems generally require an algorithmic or mathematical insight that won’t come to mind until you have significant practice under your belt. So you may find that even after a lot of practice at this level, solving problems doesn’t seem to get any easier. A learning plateau is a point where continued practice doesn’t seem to improve results.

To learn more about the ideas in this tip, see The Intermediate Plateau: What Causes It? How Can We Move Beyond It? by Scott Young.

Here are three potential causes of a LeetCode learning plateau, and how to address them.

Harder problems require exponentially more expertise

Let’s say you spend a day studying a LeetCode topic and find that you can solve most Easy questions in that topic. Spending one more day won’t get you comfortable at the Medium level. It might take a week or a month, depending on your background. And Hard problems might require multiple months, as you study several related topics and integrate them to solve those trickier challenges.

The straightforward way to build exponentially more expertise is to spend exponentially more time studying. But you also have to consider your goals. Maybe you don’t need to master Hard problems, since they are less common in real interviews. You could instead optimize for breadth of knowledge, with the goal of mastering many topics at the Medium level. This will prepare you to handle the common question types that you might encounter in an interview.

Harder problems require a different kind of thinking

The easier a problem is, the more likely it is that you can solve it by applying a pattern. As you practice, you learn different ways to solve problems. When you encounter a new problem at the Easy or Medium level, apply a pattern you know, make a few adjustments, and solve it in 30-45 minutes.

If you try this approach with Hard problems, it’s not as likely to work. You might complete a solution that works for some test cases, but find that other cases are out of reach. Your solution likely won’t be efficient enough for large cases. Or it won’t be general enough, so you’ll need to add special cases for specific tests.

To resolve this dilemma, get back to basics. Return to topics that you already know, and study them again with the benefit of experience. Make sure you really understand why every part of an algorithm works, not just how to implement it. Study the mathematical models behind the algorithm. You may need to unlearn some techniques that you relied on to solve Easy and Medium problems, and learn to solve Hard problem using more foundational methods.

Solving harder problems requires more original thinking

Learning model solutions for model problems gives you basic patterns. To write a new model solution, you’ll start with someone else’s solution and modify it so it fits your style. Then you can practice and learn it so you can apply it to new problems. It will become part of your repertoire, and if you practice it on enough problems, you may barely need to think about it. But it nevertheless started out as someone else’s solution.

Since every problem on LeetCode has one or more posted solutions, you can take the same approach with Hard problems. Look through the solutions, find a good one, and turn it into a model solution. But this is less useful for Hard problems, since aren’t as similar to each other. Once you get to the plateau of Easy and Medium problem mastery, you have to spend more time inventing your own solutions using the experience you have so far.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 41: Make Sure Your Skills Will Transfer

By Duncan Smith Leave a Comment Oct 18 0

LeetCode 2023

To get better at coding interviews, you can practice LeetCode. To get better at LeetCode, you can study algorithms and data structures. To understand how algorithms and data structures work, you can study discrete math. When you study or practice one thing in order to understand or improve another thing, you are relying on a process called transfer.

To learn more about the ideas in this tip, see How Much Transfer Should We Expect Between Skills? by Scott Young.

Increase the overlap between skills

To help a skill transfer from one context to another, make sure there is an overlap between the two contexts. Programmers practice LeetCode problems because they expect the problems they’ll encounter in an interview will resemble the ones on the LeetCode problem list. But if we only practice solving problems and submitting them to an online judge, we shouldn’t expect those skills to transfer when the interviewer asks us to explain how our solution works, since the online judge doesn’t care about that.

Take advantage of increased expertise

A benefit of gaining expertise in an area is gaining more abstract skills that transfer more efficiently to other contexts. If you learn how to solve specific problems, your skills will only transfer to problems that fit the patterns you learn. But if you learn the process of finding a solution, you can apply it to a wider variety of problems.

Learn the fundamentals

Fundamental knowledge doesn’t directly transfer to practical skills. Being able to prove that Quicksort runs in average $O(n \log n)$ time doesn’t mean you can implement Quicksort, and being able to implement Quicksort doesn’t mean you can solve any LeetCode problem that requires a divide and conquer approach. However, understanding the fundamentals can help you learn more advanced topics. So it indirectly contributes to transferrable skills.

Use the right type of practice

According to cognitive load theory, the limited capacity of working memory can impede learning if we don’t take it into account in our practice. When we first study a topic, it’s best to use examples and practice smaller components that fit into working memory. For example, it’s best to practice easier problems when learning a new algorithm. Once you can fluently implement the algorithm in its basic form, you no longer have to keep the implementation details in working memory. Your fingers know how to implement it with little input from your brain. That frees up cognitive capacity to consider the details of a more complex problem that is based on that algorithm.

Don’t just practice the target activity

To maximize transfer, it may seem like you should just practice the activity you eventually want to master. For coding interviews, this would mean spending time in mock interviews. But although this would give you a lot of practice in explaining your thought process while solving problems, it wouldn’t be the most efficient way to learn every LeetCode problem type. Learning new problem types requires concentrating on the details of the problem, without having to think about the other parts of the interview.

Similarly, the best way to learn to solve LeetCode problems isn’t to spend 100% of your study time solving problems. Time spent studying and understanding the underlying algorithms and data structures can make problem-solving practice more efficient.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 40: Learn Dynamic Programming

By Duncan Smith Leave a Comment Oct 11 0

LeetCode 2023

In the context of algorithms, dynamic programming is a technique for solving a certain type of problem by breaking it into subproblems, solving those subproblems, and using the results to find the solution to the original problem.

The purpose of this tip isn’t to explain how dynamic programming works. There are already more than enough free online resources for that purpose. Here are a few:

  • A short TopCoder tutorial: Dynamic Programming: From Novice to Advanced
  • Another short tutorial on Brilliant: Dynamic Programming
  • If you really like math: Dynamic Programming, Chapter 11 from the textbook Applied Mathematical Programming by Bradley, Hax, and Magnanti.
  • If you prefer video tutorials: Dynamic Programming by Tushar Roy (Coding Made Simple) on YouTube.
  • If you already know the basics and just want sample code: Dynamic Programming Patterns by Atalyk Akash on LeetCode.

The reason I’m bringing up dynamic programming is because, more than any other technique, it illustrates the difference between learning a solution and learning a process to find the solution.

The previous tip has an example of a DP solution pattern that applies to a specific problem type, but isn’t applicable to every DP problem. Although learning specific patterns is a good way to solve certain types of problems quickly, it’s not a viable approach for learning every LeetCode problem type. Instead, we want a process for finding a solution to any DP problem, even when one of our ready-made patterns doesn’t apply.

The rest of this tip assumes that you have some familiarity with Dynamic Programming.

Check if the technique is applicable

One sign you can use DP for a problem is that you can write a function solve(n) and inside that function you can use the result of solve(n-x) for some x. But rather than recalculating solve(n-x) as you would with a purely recursive solution, you store the result and retrieve it when you need it to evaluate solve(n). More formally, we can say that a DP problem has overlapping subproblems and optimal substructure.

Each LeetCode problem type has telltale signs showing which solution method applies. If you learn these signs for each problem type, you’ll have a better chance of knowing which approach to use.

Learn the basic patterns

To calculate solve(n), you could call solve(n-x). Then solve(n-x) could check if the result has already been calculated, and either retrieve it or calculate and store it. Alternatively, you could write a loop that iterates from 0 to n and each iteration could either use a result that has already been calculated or calculate and store the result. For DP, these two approaches are called top-down (using recursion) or bottom-up (using iteration).

Learning a basic algorithmic pattern differs from learning a specific solution, like the DP solution to the House Robber problem. Top-down and bottom-up are basic patterns, meaning every DP solution uses one of them. Similarly, other LeetCode problem types have their own patterns that define the type. For example, every binary search solution has the same fundamental structure, even if the details are different.

Apply a framework

Once you select a pattern, fill in the details using a framework. A framework helps you identify key information in the problem as you’re implementing your solution. For DP, the key information is state, which you can think of as the parameters to your solve function. For solve(n), the state is described by one variable, n. More complicated DP problems require more variables to describe the state.

Each problem type framework has details to look for. For DP, we have state variables. For binary search, we have the the range to search and how to find the midpoint. For graph problems, we have to figure out how to represent the problem data as edges and nodes.

Practice the framework using specific problems

Now we’re back in model problem and model solution territory. To learn a framework, you need to solve problems. But when you approach problems from the more general perspective of a framework rather than a specific solution example, it’s easier to apply what you know to new problems. Dynamic programming is a good place to apply this approach because it’s a flexible technique rather than a specific algorithm. It’s also easy to find tutorials that walk through the process of analyzing DP problems. And when it’s time to practice, LeetCode has plenty of DP problems. Then, once you’re comfortable with dynamic programming, you can apply the pattern/framework approach to other LeetCode problem types.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 39: Learn the Process of Finding the Solution

By Duncan Smith Leave a Comment Oct 4 0

LeetCode 2023

Learning a model solution gives you a tool to solve problems of a particular type. If you write and study a model solution for binary search, you’ll be able to solve straightforward binary search problems. Doing the same thing with other common solution types will make you better than average at coding interview problems.

But not every problem matches a standard solution approach. Hard problems require non-trivial insights, and even Medium problems often need customized solutions that build on top of a standard approach. So, while knowing model solutions will give you a head start, it won’t be enough to tackle every problem confidently. After mastering the model solution for a problem type, the next step is to learn how someone discovered that solution.

Dynamic Programming (DP) provides a good example of the difference between knowing how to solve a problem and knowing how to discover the solution. Consider the LeetCode DP problem House Robber. The scenario: A robber is planning to rob a row of houses and they know how much money is in each house. If they’re not allowed to rob two adjacent houses, what is the maximum amount of money they can steal?

This problem has a short solution, though it’s not simple to come up with. The idea is to track two values, dp1 and dp2, which both start at 0. We move through the houses from left to right. dp2 always stores the current maximum amount, the best answer we have found so far. dp1 stores the previous value of dp2 — i.e., the second best answer. At each house, we add that house’s amount to the second best answer, dp1 and see if it beats the best answer, dp2. If so, it becomes the new dp2. If not, we stay with our current dp2. Since we can’t use adjacent houses, we’re not allowed to add the current house’s value to dp2 to get an even larger amount.

To implement this algorithm, we loop through the house list and execute a modified swap operation at each house. At the end, we return the best result, dp2.

for each house in houseList
    temp = dp1
    dp1 = dp2
    dp2 = max(dp2, temp + house)

return dp2

House Robber is worth studying as a model solution because it shows how we can find an optimal solution for an entire row of houses while only considering three values: the current house amount and two sums. We don’t need to remember any previous values, so we only need O(1) space. And since we only process the list once, we only need O(n) time. Quite an efficient algorithm.

This problem is a good example of a kind of Dynamic Programming, and you can apply the solution to other problems. But although the algorithm is simple, it may not be clear how we would invent it from scratch. That’s why we want to consider how to analyze a DP problem, not just how to solve it. We’ll look at that process in the next tip.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 38: Write a Code Notebook

By Duncan Smith Leave a Comment Sep 27 0

LeetCode 2023

As we saw in Tip 31: Learn Problems and Sub-Problems, you sometimes need to learn a skill that’s smaller than a full LeetCode problem. Tip 31 explains how you can use the standard LeetCode interface to learn concepts at the sub-problem level. But there’s another way to accomplish that goal by using a REPL.

The read–eval–print loop (REPL), a tool from the early days of Lisp programming, lets you execute pieces of a program interactively. Rather than writing an entire program, compiling it, running it, and evaluating the result, you can execute a single line or block of code and see what it does immediately. This speeds up the process of learning language syntax, library functions, and the code blocks that fit together to make an algorithm implementation.

For LeetCode study, the REPL I recommend is the Jupyter Notebook. These notebooks let you write and run blocks of code surrounded by Markdown text, images, equations, and everything else you need to describe a model solution. Visual Studio Code has a convenient Jupyter extension that I use it to write my model solutions and practice journal.

A standard problem editorial, like the ones published on LeetCode, focuses on correct solutions for a particular problem. An editorial may show several ways to solve a problem, maybe with different time and space complexities. But the goal of an editorial is to describe a complete solution, like one that you could submit to LeetCode. Notebooks are a good way to document complete solutions. But they are also good for more exploratory learning.

Using a Jupyter Notebook, you can break a solution into parts and experiment with each one. In addition to “correct” code from the solution, you can show why other variations don’t work. Even runtime errors are shown in the notebook, so you can see what happens when you do something the compiler doesn’t like.

Code comments and naming conventions can help explain what is going on in a model solution. Notebooks take that a step further by letting you embed runnable blocks of code that show exactly what is happening in each part of the solution. And they let you experiment with the code in real time to help you understand exactly how it works.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 37: Understand Your Test Cases

By Duncan Smith Leave a Comment Sep 20 0

LeetCode 2023

You can’t debug a LeetCode solution without studying test cases, the textual input that every LeetCode program is required to process. The LeetCode platform verifies that every test case, whether an official test case or one you invent, meets the formatting and range specifications from the problem statement. The platform also gives you the correct output for every input. This platform behavior ensures that LeetCode test cases are reliable. Both the input and output are guaranteed to be correct.

But these platform guarantees don’t mean you can just ignore the test cases. Although you can rely on the correctness of the test cases and test results, understanding a model solution requires understanding the inputs and outputs, and a good model solution should explain the algorithm using specific test case examples.

The simplest way to use test cases is just to take what LeetCode gives you. First, make sure your program passes the sample cases. Then submit it, review the official test case results, and fix any bugs. This approach gets the job done, but it means you’re relying on someone else to design good test data, and only reviewing that data if it exposes bugs in your implementation.

To get more involved in test case production, use the random data approach: Write a short program that generates random numbers, letters, tree nodes, or whatever input the program requires. Paste that data into the LeetCode interface, run your program, and see what happens. This approach encourages you to think about the input. The amount of thinking required depends on the input criteria. For many LeetCode problems, random numbers in the appropriate range, or random lowercase letters from a to z, are good enough. Some problems require a more sophisticated test case generator. For example, if you have to generate a binary search tree, your generator has to understand BST node ordering rules. But spending time on your input generator is time well spent. To generate input, you have to understand the input requirements, which gives you a head start on your design.

Although a random test case generator lets you verify your program with as much input data as you want, you may not have enough time to generate all important inputs. If a program takes integers from $0$ to $2^{31}-1$ as input, you would have to generate a lot of data to have even a 10% chance of producing and testing both $0$ and $2^{31}-1$. (How long that would take is left as an exercise for the reader). So when you generate random data, it’s best to combine it with some hand-crafted test cases to make sure you cover all the input that might cause trouble for your program, like the extreme values in the input range.

Algorithms often perform differently on different inputs, which is why computer scientists study the best-case, worst-case, and average time complexity of algorithms. For example, a naïve QuickSort implementation can take $O(n^2)$ time to sort input that is already sorted! This is another reason to understand your input data. If you generate random data to test your QuickSort implementation, it’s unlikely that you’ll get sorted data. So you have to think about what data will best exercise your algorithm.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 36: Debugging Advice

By Duncan Smith Leave a Comment Sep 13 0

LeetCode 2023

LeetCode debugging is different from real-world debugging. But as in the real-world, you sometimes have to debug your LeetCode solutions. So it’s worth learning the techniques that work best for LeetCode-style programming.

According to the model solution approach, the goal of LeetCode practice is not just to get your submission accepted, but to learn the right way to solve problems. So the most basic form of debugging is verifying that your solution follows the design that you planned. Even when you are solving a problem for the first time, make a plan before starting implementation. The first step in debugging is comparing your code to that plan. If you’re repeating a problem, then your goal is to implement a particular model solution. Make sure your code matches what you remember about that solution.

Even if you have a model solution implementation that you could compare with your solution, it may still be worth spending time on debugging. One goal of spaced repetition is to figure out which parts of a solution are difficult, so you can focus your efforts on them. As you debug your solution, you’ll naturally find those parts. This may prompt you to re-write them so that they’re easier to understand. Or they may already be well-written, but through the debugging process, you understand and remember them better.

LeetCode problems are well-defined puzzles that are written to be solved using standard algorithms. In theory, you could even prove that your solution is correct using the tools of formal program verification. But even if you don’t go down that rabbit hole, you can check loop invariants to verify that variables have expected values at certain points. If an invariant is violated, that narrows down where in the code you need to look to find your bug.

If you can’t find any bugs but a test case is still failing, it’s not against the rules to use that test case to figure out what’s wrong with your solution. But you don’t have to use the exact test data that LeetCode gives you. LeetCode offers a feature that lets you copy a failed test case into your local test list. Then you can run it as many times as you need to without cluttering your history with failed runs. You can also modify the test case once it’s in your local list. To make it easier to track down bugs, you can make the test case smaller. Maybe your solution only has trouble with part of the test case. Try removing some data to see if you still get a failure. This approach relates to Stack Overflow’s Minimal, Reproducible Example advice. The less data you need to work with, the easier it will be to find the bug.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 35: Debug Prudently

By Duncan Smith Leave a Comment Sep 6 0

LeetCode 2023

When you write a LeetCode solution, it may have bugs. This is one way in which LeetCode programming and real-world programming are similar. But debugging your LeetCode solution doesn’t always work the same way as debugging your real-world code.

In the real world, when someone gives you a task, you’re the one person responsible for completing it. If you get stuck, you can ask your teammates or your AI buddy for help. And other developers may need to review your code before it is accepted into the product. But you are ultimately responsible for it. So when your code has a bug, it’s up to you to diagnose and fix it. It’s also up to you to decide when your code is “done” and ready to submit for review. And you are the one person writing that code for your particular product.

On LeetCode, thousands of people solve each problem, tens of people publish their solution, and one person writes an official editorial. So when you’re debugging your code, you always have the option of checking other solutions to see if you’re on the right track. And unlike real-world advice, which may not apply directly to the problem you’re trying to solve, these other solutions will be highly relevant, since they apply directly to the problem you’re working on.

This means it’s important to ask yourself at each point in the debugging process whether debugging is the best thing you could be doing with your time. If your solution is mostly correct but has a few bugs, it may be worth spending some time debugging, so you have a solution that you wrote yourself from beginning to end. But if your research tells you that your solution is unlikely to work, it’s probably better to cut your losses rather than trying to debug it into submission. Accept that you went off in the wrong direction, and learn a better way to solve the problem.

Another way that LeetCode programming differs from real-world programming is the testing process. In the real world, developers may write unit tests. But these tests, as they are written by the same person who is writing the real-world code, are often imperfect. They may not cover every important use case, or they may succeed when they should fail or fail when they should succeed.

On LeetCode, you can assume that the tests are reliable. LeetCode problems are easier to test than real-world software, since they are well-defined and constrained problems. Test cases are written and reviewed by experts, then reviewed again by LeetCoders as they try out the problems and propose new test data. So unless the problem you’re solving is very new, running your solution against the official tests cases will subject it to a rigorous test process. If your solution passes, you can conclude that it does a good job of solving the problem as specified.

But there are a few things to consider as you use official LeetCode test cases to evaluate your solution. First, unlike with Test-Driven Development (TDD) in the real world, it’s not a good idea to use LeetCode tests to drive the development of your solution. According to the TDD approach, you would run the official tests before you write any code, then write code to make the tests pass. But this isn’t what LeetCode tests are designed for. They are written to test a complete solution, not a single function. Furthermore, if you’re just trying to get tests to pass, you may end up with a solution that works for the small early tests but is not efficient enough to process larger tests fast enough.

So rather than letting tests drive your development, consider from the beginning what data you will need to handle. The “Constraints” section of the problem description will give you this information. This is also how an algorithmic coding interview works. Your interviewer will want you to provide a conceptual design for your solution. You won’t have time to build it iteratively, guided by test cases. (If you get a real-world coding problem in your interview, this advice may not apply. In that case, ask whether you should write unit tests first or at all).

Even if you complete your solution before running it against any test cases, you may still encounter a debugging snag. LeetCode will helpfully tell you that your solution failed on test 31 out of 100, which means it passed the first 30 test cases. It will even give you the test data and expected output, and let you copy the test case to your local tests. That lets you modify your solution and run it as many times as you want until it passes test #31. Once your fix is done, you can submit your solution again. Maybe this time it will fail on test #73. Repeat the process and submit again. Sometimes you’ll have to do this a few times as you discover more bugs in your solution. But make sure you aren’t just adjusting your implementation specifically for these failed tests. Ideally, you want an elegant solution that solves the general problem and avoids special cases. LeetCode test cases are designed to check for particular classes of bugs in your solution. But you want your solution to work on any valid test data because your algorithm is correct, not because you designed it to work on particular test data. So if you get more than one or two failed tests cases, consider going back to your design to see if it needs an update.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

LeetCode Tip 34: Write Your Own Textbook

By Duncan Smith Leave a Comment Aug 30 0

LeetCode 2023

The study process that I describe in these tips is based on model problems and model solutions. To write a good model solution, you can learn a process for writing it and the format that makes it most useful. The goal of LeetCode practice is to learn all the common problem types. Model solutions best support this goal when you write them in such a way that each concept is memorable. There are various techniques to make it easier to remember the concepts.

The last topic we need to consider for model solutions is the target audience. When an author writes an official LeetCode editorial or an unofficial solution in the discussion forums, they write it for a particular audience. They assume the reader is someone who wants to learn algorithmic coding interview problems, and has some basic programming experience. For an Easy problem, these may be the only prerequisites. For a Medium or Hard problem, the writer may assume knowledge of certain algorithms and data structures. Official LeetCode editorials for more advanced problems often link to a LeetCode Explore page and recommend that readers study before proceeding with the editorial.

When you write a model solution, the audience is much smaller. You can write it specifically for you. In fact, you can write it for you at a particular moment in your learning process. You can think of the model solution writing process as writing your own textbook. You are writing a textbook on LeetCode problems, a textbook that is customized for your unique background and skills and which you can update as these skills change and improve. The pages of your textbook are your model solutions, and the textbook represents everything you know about LeetCode problems.

Your textbook is organized around LeetCode problems because learning through problem-solving is the LeetCode philosophy. You choose the problems based on which concepts you need to learn and which problems are best suited to explore each concept. Taken as a whole, the problems in your textbook form a web of knowledge that covers the full set of LeetCode concepts that you have studied so far. The model solutions should reference each other so that as you learn one problem you also get better at related problems, even those that focus on different concepts.

Although problems are the organizing principle of your textbook, the fundamental concepts that you are learning are algorithms and data structures. The problems and the concepts mutually reinforce each other. As you learn your model solutions, you learn more about algorithms and data structures. And as you learn about algorithms and data structures, you get better at solving LeetCode problems. Although the goal is to show your problem-solving skills in an interview, the best way to remember how to solve these problems and apply the solutions to new problems is to understand every detail of how each problem works, both the theory behind the solution and the implementation details required to complete a working solution.

Like other types of textbooks, your LeetCode textbook will change over time. But unlike a textbook author, who might release a new edition every few years, you can update your textbook every day using that day’s practice session. Every repetition of a problem should point out a gap in your knowledge. (If it doesn’t, it means you’re done with a problem and you should remove it from your repetition queue). When you identify a gap, update your textbook with what you need to work on. And when you fill the gap through research and practice, update your textbook so you don’t forget your hard-won knowledge.

This year, I’m publishing a series of tips for effective LeetCode practice. To read the tips in order, start with A Project for 2023.

  • « Previous Page
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • …
  • 50
  • Next Page »

Stay in the Know

I'm trying out the latest learning techniques on software development concepts, and writing about what works best. Sound interesting? Subscribe to my free newsletter to keep up to date. Learn More
Unsubscribing is easy, and I'll keep your email address private.

Getting Started

Are you new here? Check out my review posts for a tour of the archives:

  • 2023 in Review: 50 LeetCode Tips
  • 2022 in Review: Content Bots
  • 2021 in Review: Thoughts on Solving Programming Puzzles
  • Lessons from the 2020 LeetCode Monthly Challenges
  • 2019 in Review
  • Competitive Programming Frequently Asked Questions: 2018 In Review
  • What I Learned Working On Time Tortoise in 2017
  • 2016 in Review
  • 2015 in Review
  • 2015 Summer Review

Archives

Recent Posts

  • Will AI Coding Assistants “Deskill” Us? January 30, 2026
  • Stateless by Design: How to Work With AI Coding Assistants December 31, 2025
  • Do Coding Bots Mean the End of Coding Interviews? December 31, 2024
  • Another Project for 2024 May 8, 2024
  • Dynamic Programming Wrap-Up May 1, 2024
  • LeetCode 91: Decode Ways April 24, 2024
  • LeetCode 70: Climbing Stairs April 17, 2024
  • LeetCode 221: Maximal Square April 10, 2024
  • Using Dynamic Programming for Maximum Product Subarray April 3, 2024
  • LeetCode 62: Unique Paths March 27, 2024
Red-Green-Code
  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter
Copyright © 2026 Duncan Smith