Every senior developer follows a structured approach to problem-solving. Whether you are debugging a production issue or building a new feature, these seven steps will help you arrive at a clean, efficient solution every time.
We will walk through each step using a real-world example: find duplicate values in a list.
Read every detail carefully. Clarify inputs, outputs, constraints, and edge cases before writing a single line of code. Ask yourself:
Our example: Given a list of integers, return all values that appear more than once. Input: [1, 3, 5, 3, 7, 5] → Output: [3, 5]. The list can be empty. Duplicates should appear only once in the result.
Create multiple test cases before you code. Good examples expose edge cases you would otherwise miss.
[1, 2, 3] → [] (no duplicates)[1, 1, 1] → [1] (all same)[] → [] (empty input)[4, 4, 5, 5, 6] → [4, 5] (multiple duplicates)Get a working solution first. Do not optimize prematurely. A brute-force approach compares every element against every other element. This runs in O(n²) time.
public List<Integer> findDuplicatesBrute(int[] nums) {
List<Integer> result = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] == nums[j] && !result.contains(nums[i])) {
result.add(nums[i]);
}
}
}
return result;
}
def find_duplicates_brute(nums):
result = []
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] == nums[j] and nums[i] not in result:
result.append(nums[i])
return result
It works. It is correct. That is all that matters at this stage.
Now analyze the brute-force approach and look for improvements. The nested loop is the bottleneck. A HashSet lets us track seen elements in O(1) time, reducing overall complexity to O(n).
The trade-off: we use extra memory (a set) to gain speed. This is almost always worth it.
public List<Integer> findDuplicates(int[] nums) {
Set<Integer> seen = new HashSet<>();
Set<Integer> duplicates = new HashSet<>();
for (int num : nums) {
if (!seen.add(num)) {
duplicates.add(num);
}
}
return new ArrayList<>(duplicates);
}
def find_duplicates(nums):
seen = set()
duplicates = set()
for num in nums:
if num in seen:
duplicates.add(num)
seen.add(num)
return list(duplicates)
O(n) time, O(n) space — a significant improvement over the O(n²) brute force.
Trace through the optimized code with a concrete example before implementing it in production. Using [1, 3, 5, 3, 7, 5]:
1 → not in seen, add to seen. seen={1}3 → not in seen, add to seen. seen={1,3}5 → not in seen, add to seen. seen={1,3,5}3 → already in seen, add to duplicates. duplicates={3}7 → not in seen, add to seen. seen={1,3,5,7}5 → already in seen, add to duplicates. duplicates={3,5}Result: [3, 5]. The logic is correct. We are ready to implement.
Write clean, production-ready code. Use clear names, handle edge cases, and keep methods focused on a single responsibility.
import java.util.*;
public class DuplicateFinder {
public static List<Integer> findDuplicates(List<Integer> nums) {
if (nums == null || nums.isEmpty()) {
return Collections.emptyList();
}
Set<Integer> seen = new HashSet<>();
Set<Integer> duplicates = new LinkedHashSet<>();
for (int num : nums) {
if (!seen.add(num)) {
duplicates.add(num);
}
}
return new ArrayList<>(duplicates);
}
}
def find_duplicates(nums: list[int]) -> list[int]:
if not nums:
return []
seen = set()
duplicates = []
for num in nums:
if num in seen and num not in duplicates:
duplicates.append(num)
seen.add(num)
return duplicates
Notice the use of LinkedHashSet in Java and an ordered list in Python to preserve insertion order of duplicates — a detail that shows attention to quality.
Write tests that cover normal cases, edge cases, and boundary conditions. Do not skip this step.
@Test
void testFindDuplicates() {
assertEquals(List.of(3, 5),
DuplicateFinder.findDuplicates(List.of(1, 3, 5, 3, 7, 5)));
assertEquals(List.of(),
DuplicateFinder.findDuplicates(List.of(1, 2, 3)));
assertEquals(List.of(1),
DuplicateFinder.findDuplicates(List.of(1, 1, 1)));
assertEquals(List.of(),
DuplicateFinder.findDuplicates(List.of()));
}
def test_find_duplicates():
assert find_duplicates([1, 3, 5, 3, 7, 5]) == [3, 5]
assert find_duplicates([1, 2, 3]) == []
assert find_duplicates([1, 1, 1]) == [1]
assert find_duplicates([]) == []
Cover all the test levels: unit tests for individual methods, integration tests for component interactions, and functional tests for end-to-end flows. Small, focused test cases run faster and catch bugs just as effectively as large ones.
Solving problems is a skill you build through repetition. Follow these seven steps consistently and you will write cleaner code, catch more edge cases, and deliver faster. The pattern is always the same: understand, example, brute force, optimize, trace, implement, test.