Data structures and algorithms (DSA) help us organize data and solve problems efficiently. In coding interviews, we need to choose an approach, explain why it works, and turn it into code. This article brings together 40 DSA problems to practice those skills. Each problem has a link to its full statement and a short hint to help when we get stuck.
We start with arrays and hash maps, then move to trees, graphs, and other topics. Each group introduces an idea and then gives us variations to practice. We can solve the problems in Java or another language we already know. The aim is to understand the solution well enough to write it again without copying earlier code.

1. Which DSA Problems Should You Practice First?
If we are starting out, these seven problems give us a useful foundation.
Two Sum, Valid Anagram, Best Time to Buy and Sell Stock, Binary Search, Valid Parentheses, Reverse Linked List, and Maximum Depth of Binary Tree introduce the ideas used throughout this list. We learn how to look up a value, scan an array, search sorted data, use a stack, change links between nodes, and work through a tree.
After these seven problems, we work through the remaining problems in the order shown below. The numbers refer to this article, not to the problem numbers on LeetCode.
Each problem has a practice label. Start means it is a good first attempt for that topic. Build adds another idea. Extend gives us a variation to try once we understand the earlier problems. These labels describe our practice order, not LeetCode difficulty ratings.
| Practice order | Problems | What you learn |
|---|---|---|
| Arrays and hashing | 1-4 | Remember values you have already processed |
| Two pointers and sliding windows | 5-8 | Move through a sequence without checking every possible range |
| Binary search | 9-12 | Discard a part of the search space safely |
| Stacks and linked lists | 13-18 | Track unfinished work and change node links |
| Trees | 19-22 | Solve a structure through its smaller subtrees |
| Heaps and intervals | 23-26 | Keep selected candidates and combine overlapping ranges |
| Graphs | 27-30 | Explore connections, components, and dependencies |
| Backtracking | 31-34 | Build choices, explore them, and undo them |
| Dynamic programming and greedy choices | 35-40 | Reuse smaller answers and justify local decisions |
Before starting a group, we need to know how to use its data structure in our chosen language. For example, we need to add and find map entries, push and pop stack items, and add and remove queue items. If these operations are unfamiliar, we practice them before attempting the problems.
We first attempt each problem without reading its hint. If we are preparing for an interview, we can also hide the topic heading. This gives us practice in choosing an approach without being told which one to use.
2. Arrays and Hashing Problems
A hash map stores a value under a key. For example, we can store an array element as the key and its index as the value. Later, we can look up that element without searching the array again. The first three problems practice choosing useful keys and values. The fourth uses products from the left and right sides of an array.
| No. | Problem | Practice focus and starting hint |
|---|---|---|
| 1. Start | Two Sum | We need two different array positions whose values add to the target. For each value, we look for the required partner in a map before storing the current value and its index. |
| 2. Start | Valid Anagram | We check whether both strings contain the same characters the same number of times. We can count each character and compare the counts. The allowed characters determine how we store those counts. |
| 3. Build | Group Anagrams | We put words that are anagrams into the same group. We can sort the letters of each word and use the sorted string as a map key. |
| 4. Extend | Product of Array Except Self | For each position, we need the product of every other element without using division. We combine the product of the elements on its left with the product of those on its right. |
In Two Sum, we need two different array positions. For [3, 3] and target 6, we use both positions. At each element, we first look for its partner in the map and then add the current element. This order prevents us from using the same position twice.
We visit each element once. When map operations take constant time on average, the whole solution takes linear time.
For Product of Array Except Self, we test an array with one zero and another with two zeros. These cases help us check that the solution handles zero correctly. Dividing a total product is not a valid approach because the statement does not allow division.
3. Two Pointers and Sliding Window Problems
Two pointers let us work with two positions in an array or string. For example, one pointer can start at each end of a sorted array. A sliding window uses two boundaries to track a continuous part of the input. We move those boundaries as we add or remove elements.
For each problem, we need to explain why moving a boundary does not skip a possible answer.
| No. | Problem | Practice focus and starting hint |
|---|---|---|
| 5. Start | Best Time to Buy and Sell Stock | We need the largest profit from buying once and selling on a later day. As we scan the prices, we remember the lowest earlier price and the best profit found so far. |
| 6. Build | Two Sum II – Input Array Is Sorted | We need two positions in a sorted array whose values add to the target. We place one pointer at each end and use the sum to decide which pointer to move. The returned positions start at 1. |
| 7. Build | Longest Substring Without Repeating Characters | We need the longest part of a string with no repeated character. We move the right boundary forward and move the left boundary past any repeat. The left boundary must never move backward. |
| 8. Extend | 3Sum | We need all unique groups of three values that add to zero. We sort the array, fix one value, and use two pointers to find the other two. We skip duplicates to avoid repeating an answer. |
For Longest Substring Without Repeating Characters, we can test abba. When we reach the second b, we move the left boundary past the first b. When we later reach a, we must not move the boundary backward. The earlier a is already outside the window.
A sliding window does not work for every problem involving an array range. For example, we cannot always reduce a sum by removing an element from the left. Removing a negative number increases the sum. We need to check the allowed values before choosing this approach.
4. Binary Search Problems
Binary search checks the middle of a range and uses the result to remove one side. In a sorted array, if the middle value is smaller than the target, the target cannot be anywhere to its left. We repeat this process until we find the answer or no positions remain.
The important part is explaining why we can safely remove a side. The loop comes after that reasoning.
| No. | Problem | Practice focus and starting hint |
|---|---|---|
| 9. Start | Binary Search | We look for a target in a sorted array. After checking the middle value, we keep only the side that could contain the target. We use one consistent rule for the range endpoints. |
| 10. Build | Find First and Last Position of Element in Sorted Array | We need the first and last positions of a target that may appear more than once. We use separate searches for the two boundaries instead of stopping at the first match. |
| 11. Build | Search in Rotated Sorted Array | We search an array that was sorted before being rotated. At each step, we identify the sorted half and check whether the target could be inside it. |
| 12. Extend | Koko Eating Bananas | We need the smallest eating speed that finishes all piles within the time limit. We try a speed, calculate the hours it needs, and use binary search to narrow the possible speeds. |
Koko Eating Bananas uses binary search on the answer. Here, we search possible eating speeds instead of array positions. If one speed finishes all the bananas in time, a higher speed also works. We use that rule to find the smallest speed that works. The piles themselves do not need to be sorted.
For array searches, we test a missing target, a one-element array, and targets at either end. We can calculate the middle index as the lower bound plus half the difference between the bounds. This avoids adding two large indices, which could overflow an integer.
5. Stack and Linked List Problems
A stack returns the most recently added item first. This makes it useful for matching brackets or keeping track of days that are still waiting for a warmer temperature.
A linked list stores nodes connected by references. When we change one reference, we must keep a way to reach the rest of the list. The linked list problems below practice making these changes safely.
| No. | Problem | Practice focus and starting hint |
|---|---|---|
| 13. Start | Valid Parentheses | We check whether the brackets are correctly matched and nested. A stack remembers opening brackets. Each closing bracket must match the latest opening bracket, and none may remain at the end. |
| 14. Build | Daily Temperatures | For each day, we need the number of days until a warmer temperature. A stack holds indices of days still waiting for an answer. A warmer day lets us answer and remove earlier days. |
| 15. Start | Reverse Linked List | We reverse the links between the nodes. Before changing a node’s next reference, we save its next node so we can continue through the list. |
| 16. Build | Merge Two Sorted Lists | We combine two sorted lists into one sorted list. We repeatedly take the smaller current node. A temporary starting node makes it easier to attach the first result node. |
| 17. Build | Linked List Cycle | We check whether following next references eventually repeats a node. We move one pointer faster than the other. If there is a cycle, the two pointers meet inside it. |
| 18. Extend | Remove Nth Node From End of List | We remove the node that is n positions from the end. We keep a gap between two pointers so that one reaches the end when the other reaches the needed position. We also handle removing the head. |
Daily Temperatures uses a monotonic stack. This means we keep the stack in a particular value order. In this problem, the temperatures do not increase from the bottom to the top. A warmer day removes the earlier days whose answers it provides.
Each index enters the stack once and leaves it at most once. The total work is therefore linear, even if one day removes several earlier indices.
Before coding a linked list solution, we can draw a list with three nodes. We label the current node, the next node, and any other references we need. This helps us see whether changing a link would make part of the list unreachable.
6. Binary Tree Problems
Each node in a binary tree has at most two children. We can often solve a tree problem by first solving it for the left and right subtrees. The method calls itself for each subtree, which is called recursion.
Before writing the method, we define what it returns for one node and for an empty subtree. This makes it easier to combine the answers from the children.
| No. | Problem | Practice focus and starting hint |
|---|---|---|
| 19. Start | Maximum Depth of Binary Tree | We find the number of nodes on the longest path from the root to a leaf. For each node, we take the larger child depth and add one. An empty subtree has depth zero. |
| 20. Build | Binary Tree Level Order Traversal | We return the node values one level at a time. A queue holds nodes to visit. At the start of a level, its current size tells us how many nodes belong to that level. |
| 21. Build | Validate Binary Search Tree | We check whether every node follows the binary search tree rules. We pass down the allowed lower and upper values from earlier nodes. Checking only a node’s immediate children is not enough. |
| 22. Extend | Lowest Common Ancestor of a Binary Tree | We find the lowest node whose subtree contains both target nodes. We search its left and right subtrees and combine what the two calls find. |
In a binary search tree, every value in a node’s left subtree must be smaller than that node. If the root is 10, the value 12 cannot appear anywhere in its left subtree. Checking only whether each child is smaller or larger than its immediate parent would miss this error.
Recursion also uses memory for method calls that have not yet returned. For a tree traversal, this can grow with the height of the tree. If the tree is shaped like a chain, its height can equal its number of nodes.
7. Heap and Interval Problems
A heap lets us repeatedly access the smallest or largest stored value. It is useful when we need to keep the best few values seen so far.
An interval is a range with a start and an end. We often sort intervals by one of these values. We can then compare nearby intervals to decide whether they overlap or which one to keep.
| No. | Problem | Practice focus and starting hint |
|---|---|---|
| 23. Start | Kth Largest Element in an Array | We find the kth largest value, counting duplicates separately. A min-heap can keep the largest k values seen so far. When it holds k values, its smallest value is the kth largest so far. |
| 24. Build | Top K Frequent Elements | We return the k values that appear most often. We first count their occurrences. We can then select the answers with a heap or group values by how often they occur. |
| 25. Start | Merge Intervals | We combine overlapping ranges. We sort them by start value and compare each range with the last result. If they overlap, we extend the result to the larger end value. |
| 26. Extend | Non-overlapping Intervals | We remove as few ranges as possible so the remaining ranges do not overlap. When two ranges overlap, keeping the one that ends earlier leaves more room for later ranges. |
The problem statement decides whether touching endpoints count as an overlap. In Merge Intervals, [1, 4] and [4, 5] merge into [1, 5]. In Non-overlapping Intervals, the same two intervals can remain separate. We need a different comparison for these two rules.
8. Graph Problems
A graph represents items as nodes, also called vertices. An edge connects two nodes. A grid can also be treated as a graph, with each cell connected to its allowed neighbors.
Depth-first search follows one path before returning to try another. Breadth-first search visits nearby nodes first, then nodes farther away. We choose the search based on what the problem asks us to find.
| No. | Problem | Practice focus and starting hint |
|---|---|---|
| 27. Start | Number of Islands | We count separate regions of land in a grid. From each unvisited land cell, we search all land connected horizontally or vertically and mark it as visited. |
| 28. Build | Clone Graph | We create new nodes with the same connections as the original graph. A map links each original node to its copy. We record the copy before visiting neighbors so a cycle does not make us copy endlessly. |
| 29. Build | Rotting Oranges | We find how many minutes it takes for rot to reach all reachable fresh oranges. We put every initially rotten orange into the queue first. Breadth-first search then follows the spread one minute at a time. |
| 30. Extend | Course Schedule | We check whether all courses can be completed while respecting prerequisites. We count each course’s remaining prerequisites and process courses whose count is zero. |
In Number of Islands, we start a search when we find an unvisited land cell. That search marks all connected land as visited. We count one island for the whole search, not one for each cell. We start another search only when we find land that has not been visited.
In Course Schedule, we first process courses with no remaining prerequisites. Each completed course reduces the prerequisite count of the courses that depend on it. If we process every course, the schedule is possible. If courses remain but none is ready, a cycle prevents us from finishing.
This approach is called topological sorting. It puts each prerequisite before the courses that need it.
9. Backtracking Problems
Backtracking tries one choice at a time. We add a choice to the current answer, explore what follows, and then remove it before trying another choice. For example, when building a subset, we can include a value and later try leaving it out.
The important step is restoring any data we changed. Otherwise, one attempt can affect the next.
| No. | Problem | Practice focus and starting hint |
|---|---|---|
| 31. Start | Subsets | We generate every subset of distinct values. At each position, we try including the value and leaving it out. |
| 32. Build | Permutations | We generate every ordering of distinct values. While building an ordering, we track which values it already uses so none is chosen twice. |
| 33. Build | Combination Sum | We find combinations that add to the target. A candidate may be used more than once. We keep a starting index so different orders of the same combination are not repeated. |
| 34. Extend | Word Search | We check whether neighboring grid cells can spell a word. A cell can appear only once in the current path. We mark it while exploring and restore it when we return. |
Subsets and Permutations ask us to return every valid answer. With n distinct values, there are 2^n subsets and n! permutations. The output becomes large quickly, even with an efficient solution. When explaining time complexity, we also count the work needed to copy each answer into the result.
In Word Search, we mark a cell as visited only for the path we are currently trying. When we return from that path, we restore the cell. Another path may need to use it.
10. Dynamic Programming and Greedy Problems
Dynamic programming saves answers to smaller problems so we do not calculate them again. For example, the best answer for one amount of money can help us find the answer for a larger amount.
A greedy algorithm makes a choice at the current step and continues from there. Before using it, we need to explain why that choice still allows the best final answer. A choice that looks good now is not always correct.
| No. | Problem | Practice focus and starting hint |
|---|---|---|
| 35. Start | Climbing Stairs | We count the ways to reach the top when each move takes one or two steps. The count for a step comes from the counts for the previous two steps. We define the starting counts first. |
| 36. Build | House Robber | We find the largest amount that can be collected without choosing neighboring houses. At each house, we compare skipping it with taking it and adding the best amount from two houses earlier. |
| 37. Build | Coin Change | We find the fewest coins needed for an amount. We save the best count for each smaller amount. We also need a clear value to mark amounts that cannot be formed. |
| 38. Build | Maximum Subarray | We find the largest sum from a nonempty continuous part of the array. At each value, we choose whether to extend the previous range or start a new one there. |
| 39. Extend | Longest Increasing Subsequence | We find the longest strictly increasing sequence while allowing positions to be skipped. We can first compare each value with earlier values. Once that solution is clear, we can study the faster binary-search approach. |
| 40. Extend | Jump Game | We check whether we can reach the last array position. We track the farthest position reachable so far. If the next position is beyond that limit, we cannot continue. |
Coin Change shows why a greedy choice needs a reason to work. With coins [1, 3, 4] and amount 6, choosing the largest coin first gives 4 + 1 + 1. That uses three coins. The answer 3 + 3 uses only two. We cannot assume that a greedy choice is correct just because it works for a few examples.
For Maximum Subarray, we test an array that contains only negative numbers. The answer must still contain at least one element. Starting the best sum at zero could incorrectly return the sum of an empty range.
11. How to Practice Each Problem
A successful submission tells us that the code passed the platform’s tests. To check our understanding, we also need to explain the approach and write it again without the old solution. We should be able to say why it works for every allowed input.
We can follow these six steps for each problem.
- We explain the task in our own words. We identify the required output, the allowed inputs, and whether the solution may change the input.
- We work through an example. We also try an important test case allowed by the statement, such as repeated values, one element, no solution, or all-negative values.
- We describe a basic solution. We explain how it finds the answer and which work it repeats as the input grows.
- We look for a way to avoid repeated work. We consider what to store, which positions we can skip, or which smaller answers we can reuse.
- We write and test the code. We check the given examples and our own test cases. We explain how runtime and memory use grow with the input, including recursion and output where needed.
- We solve the problem again later. We close the old solution and rebuild it ourselves. If we get stuck, we record the idea we could not remember and revisit it.
When we get stuck, we first identify the problem. Do we need an approach, an explanation of why it works, help with the code, or a fix for one test case? We then read a hint that addresses that part. If we read a full solution, we close it and write our own version afterward.
12. A Practice Schedule You Can Adjust
The schedule below gives us an order to follow. It does not require us to finish a group in a fixed number of days. We move on when we can solve its starting problems and explain the important test cases. If we still depend on remembering code, we spend more time on that group.
| Stage | New practice | Revision |
|---|---|---|
| 1 | Arrays, hashing, two pointers, and windows, problems 1-8 | Reattempt Two Sum and the longest-substring problem without hints |
| 2 | Binary search, stacks, and linked lists, problems 9-18 | Explain why each search boundary or pointer movement is safe |
| 3 | Trees, heaps, intervals, and graphs, problems 19-30 | Reconstruct a tree traversal and a graph traversal from a blank file |
| 4 | Backtracking, dynamic programming, and greedy choices, problems 31-40 | Mix problems from earlier groups without seeing their topic labels |
If preparation time is short, we can solve the Start problems first. We then choose Build problems from the topics we find difficult. If we already know the main approaches, we can mix problems from different groups. Our mistakes show us which topics need more practice.
We keep a short record of each problem, the approach we used, what went wrong, and when to try it again. A note such as “moved the window start backward” tells us what to check next time. A general note such as “needs practice” does not.
13. Conclusion
These 40 problems give us practice with the main ideas used in coding interviews. We start with the simpler problems and use the later variations to check our understanding. For every solution, we explain which input conditions make it work. For example, a two-pointer solution may depend on the array being sorted.
Once we finish the groups, we attempt problems without looking at their topic labels. We explain the approach before coding. This helps us check whether we can choose a solution ourselves, as we would need to do in an interview.
14. References
Each problem link provides the full statement, allowed inputs, and examples. When we want more practice after these 40 problems, we can use the following official study plans.
Happy Learning!