๐Ÿ“˜ Phase A ยท Module 2

Problem-Solving Mindset

โฑ ~10 hours ๐Ÿ“… Week 2 ๐ŸŽฏ The skill AI can't replace

By the end of this module you will:

Decomposition Big-O intuition Hash maps Sliding window Two pointers Pythonic patterns

The only skill AI genuinely can't replace

AI tools are extraordinary at generating code. They're considerably less reliable at thinking about problems. Given a vague prompt, they'll produce plausible-looking code that may be solving the wrong problem, or solving the right problem inefficiently. The person who catches this is the engineer who can think clearly about what the problem actually is before any code is written.

This is the skill that makes the difference between a vibe coder who gets into trouble and an agentic engineer who catches the trouble before it ships.

The four questions before you write anything

Whenever you get a problem โ€” from a colleague, from your own head, or from an AI agent you're directing โ€” ask these four questions first:

  1. What is the input? What data do I receive? What are the edge cases (empty, null, very large, negative, duplicate)?
  2. What is the output? What exact form should the result take?
  3. What are the constraints? Does this need to be fast? Does it run in a tight loop? Is memory limited?
  4. What's the simplest correct solution first? Not the cleverest โ€” the simplest. Optimize only if needed.

This sounds obvious. It is obvious. And most bugs exist because someone skipped these questions.

Big-O: what you actually need to know

You don't need to derive Big-O proofs. You need intuition โ€” the ability to look at a piece of code and feel whether it will be fast or slow. Here's the practical version:

The intuition table

Complexity What it means Spot this pattern
O(1) Instant โ€” doesn't depend on input size Dict lookup, list index access
O(log n) Very fast โ€” halves the problem each step Binary search, sorted tree lookup
O(n) Linear โ€” one pass through the data Single loop, list traversal
O(n log n) Good for sorting Python's sorted()
O(nยฒ) Slow on large inputs โ€” gets bad fast Nested loops โ€” this is the one to spot
O(2โฟ) Terrible โ€” doubles with every new element Recursive functions without memoization
The pattern to spot in code reviews

A nested loop over the same list is O(nยฒ). It works fine for 100 items. It becomes painfully slow at 10,000 items. AI tools generate nested loops constantly when a hash map lookup would be O(n). Spotting this and suggesting the fix is a real, valued skill.

The hash map replacement

The single most common performance upgrade in code review: replace a "find X in list" with a dict lookup.

# โŒ O(nยฒ) โ€” for each user, scan the ENTIRE permissions list
def get_user_permissions(users: List[User], permissions: List[Permission]):
    result = []
    for user in users:
        for perm in permissions:     # nested loop = O(nยฒ)
            if perm.user_id == user.id:
                result.append((user, perm))
    return result

# โœ“ O(n) โ€” build the lookup dict once, then use it
def get_user_permissions(users: List[User], permissions: List[Permission]):
    perm_by_user = {p.user_id: p for p in permissions}  # O(n) once
    return [
        (user, perm_by_user[user.id])
        for user in users
        if user.id in perm_by_user     # O(1) per lookup
    ]

Three patterns worth knowing cold

These three patterns solve a surprising fraction of coding problems. Recognising which one applies is the decomposition skill.

1. Hash map โ€” "I need to look something up fast"

Any time a problem involves finding duplicates, counting occurrences, grouping items, or checking if something exists โ€” reach for a dict first.

2. Two pointers โ€” "I need to compare elements across a sorted list"

Two pointers that move toward each other (or in the same direction at different speeds) replace nested loops for many array problems.

# Find two numbers that sum to target โ€” two pointer approach
def two_sum_sorted(nums: List[int], target: int) -> Optional[Tuple[int, int]]:
    left, right = 0, len(nums) - 1
    while left < right:
        total = nums[left] + nums[right]
        if total == target:
            return (nums[left], nums[right])
        elif total < target:
            left += 1
        else:
            right -= 1
    return None

3. Sliding window โ€” "I need the best/sum/max over a sub-range"

When you need to examine every contiguous subarray of a fixed size, a sliding window avoids recomputing from scratch each time.

# Maximum sum of any 3 consecutive numbers โ€” sliding window
def max_sum_window(nums: List[int], k: int) -> int:
    window_sum = sum(nums[:k])
    max_sum = window_sum
    for i in range(k, len(nums)):
        window_sum += nums[i] - nums[i - k]  # add new, remove old
        max_sum = max(max_sum, window_sum)
    return max_sum

Decomposition in practice

Here's how you apply the "four questions" to a real problem before writing code. Imagine you're asked: "Given a list of load records, find all flights where the actual weight exceeds the maximum by more than 10%."

  1. Input: A list of dicts with at least flight_number, actual_weight, max_weight. Edge cases: empty list, records where max_weight is 0 (division by zero!), missing keys.
  2. Output: A list of flight numbers (strings), possibly with the excess percentage.
  3. Constraints: Could be thousands of records โ€” O(n) is fine, but no nested loops over the same list.
  4. Simplest solution: A single list comprehension with a guard for max_weight > 0.
def find_overloaded_flights(
    records: List[Dict[str, float]],
    threshold_pct: float = 10.0
) -> List[str]:
    """Return flight numbers where actual weight exceeds max by threshold_pct%."""
    return [
        r["flight_number"]
        for r in records
        if r.get("max_weight", 0) > 0
        and (r["actual_weight"] - r["max_weight"]) / r["max_weight"] * 100 > threshold_pct
    ]

Notice: the division-by-zero guard came from step 1 (edge cases). Without the four questions, that bug would have been in the code.

The Codewars habit

Codewars presents small, well-defined programming puzzles. You solve them, then see how other people solved them. That second step โ€” reading other solutions after you've written yours โ€” is where the real learning happens.

The daily routine: one kata before you start work. Takes 10โ€“15 minutes. After solving it, read the top-voted solutions. Ask yourself: why is their version more elegant? What did they know that I didn't?

Start at 8 kyu (easiest) and work your way up. Once you're consistently solving 6 kyu with clean solutions, you're ready for Module A3.

Resources for this module

๐Ÿ’ป Exercise โ€” do this with Cody

Open Claude Code and say: /learn A2

Solve 10 Codewars katas at 8โ€“6 kyu. Paste each solution to Cody for review. Cody will suggest the more idiomatic Python version, explain the Big-O of both, and flag any patterns worth knowing.

๐Ÿ”
Flaw Drill โ€” identify the performance problem
This code works. But it shouldn't be written this way.
def find_duplicates(items: List[str]) -> List[str]:
    duplicates = []
    for i, item in enumerate(items):
        for j, other in enumerate(items):
            if i != j and item == other and item not in duplicates:
                duplicates.append(item)
    return duplicates

Questions to answer: (1) What's the Big-O? (2) Rewrite it in O(n). (3) Is there a one-liner Python version? Then verify with /learn A2 flaw-drill.

โ† A1: Python Deep Next: Git & GitHub โ†’