How to tackle a problem

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.

1. Understand the Problem

Read every detail carefully. Clarify inputs, outputs, constraints, and edge cases before writing a single line of code. Ask yourself:

  • What is the input type and size?
  • What should the output look like?
  • Are there constraints on time or space?

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.

2. Have Examples of the Problem

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)

3. Solve with Brute Force

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.

4. Optimize Your Solution

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.

5. Walk Through Your Solution

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.

6. Implement Your Solution

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.

7. Test Your Solution

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.

Key Takeaway

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.




Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *