emrevarol.com Materials
A2SV
def solve(problem): understand(problem) manual_solve(example) design_algorithm() attack_own_idea() implement() test_comprehensively() simplify() return solution class Interview: def __init__(self): self.steps = 7 self.clarity = True self.speed = False # The Playbook for step in range(1, 8): execute(step) communicate(step) verify(step)

7 Steps of Highly Effective
Problem Solving

Igitabo cy'Ikiganiro

A2SV - Emre Varol

Kubera Iki Igitabo?

  • Interviews create pressure
  • Pressure destroys random thinking
  • A playbook creates consistency
  • Consistency beats occasional brilliance
Random
↗ ↙ ↖ ↘
↓ ↑ → ←
↙ ↗ ↘ ↖
Structured
1. Understand 2. Manual 3. Design 4. Attack 5. Code 6. Test 7. Clean

Not just to solve problems -
to solve them reliably under pressure

Better judgment
Fewer avoidable mistakes
Cleaner communication
More interview wins

Intambwe 7

1
Understand the problem
2
Solve the problem manually
3
Come up with a solution idea
4
Attack your own idea
5
Implement
6
Test comprehensively
7
Simplify and clean up

Ikosa Rinini ry'Umukandida

Starting to code too early

Consequences

  • Wrong understanding
  • Wrong algorithm
  • Wrong complexity
  • Wasted time
01
Kumva Ikibazo
  • Read slowly
  • Process every sentence
  • Extract constraints
  • Restate in your own words

Step 1: Good vs Bad

Bibi

  • Skims the problem
  • Assumes what is being asked
  • Starts coding after reading once

Byiza

  • Restates input and output
  • Identifies constraints
  • Confirms what exactly must be returned

Problem Example: Two Sum

Given an array of integers nums and an integer target, return the indices of the two numbers whose sum equals target. Assume exactly one valid answer exists, and the same element cannot be used twice.
target = 9
2
0
7
1
11
2
15
3
2 + 7 = 9 → return [0, 1]

Good Step 1 on Two Sum

Candidate says:

  • Input: array + target
  • Output: two indices
  • We return positions, not values
  • Exactly one answer exists
  • Cannot reuse the same element
No ambiguity left.
Important constraints captured.

Bad Step 1 on Two Sum

Bad Candidate Behavior

  • "I just need two numbers that add up"
  • Forgets indices vs values
  • Ignores exactly-one-answer constraint
  • Ignores can't-use-same-element rule

Correct-looking code can still be wrong.

02
Solve the Problem Manually
"If you cannot do it by hand, you cannot automate it."
  • Start with the sample
  • Track your decisions
  • Look for repetition
  • Extract the pattern

Step 2: Good vs Bad

Bad

  • Jumps from reading to coding
  • Says "I think I know the trick"
  • Never checks a small example

Good

  • Works through sample input
  • Notices repeating action
  • Extracts what information would help

Manual Walkthrough: Two Sum

nums = [2, 7, 11, 15], target = 9
2
0
Need 7
7
1
Answer: [0, 1]
Pattern discovered: For each number, I need its complement.

"What am I repeatedly checking by hand?"

For Two Sum: "Does the needed number already exist?"

hash map fast lookup O(n) solution
03
Come Up with a Solution Idea
  • Generalize your manual process
  • Choose the right data structure
  • Check time complexity
  • Check space complexity
  • Fill logic gaps before coding

The Complexity Gate

Before writing code, ask:

  • Is brute force good enough?
  • What do the constraints allow?
  • Are helper functions hiding extra cost?
  • Am I sure this can pass?

No code before this gate.

Step 3: Good vs Bad

Bad O(n²)

  • "I'll just code and see"
  • No complexity discussion
  • Vague logic
  • Data structure chosen randomly

Good O(n)

  • States brute force first
  • Improves it intentionally
  • Picks structure based on need
  • Explains why it fits constraints

Two Sum: Good Design

Brute Force

Try every pair

O(n²)

Better

Store seen numbers in dict, check complement

O(n)

improvement

Python: Two Sum Core Idea

def two_sum(nums, target): seen = {} for i, num in enumerate(nums): need = target - num if need in seen: return [seen[need], i] seen[num] = i
Short
Readable
Structure matches need
Complexity is clear
04
Attack Your Own Idea
"Play devil's advocate. Imagine your worst enemy came up with this idea. You would do your best to tear it apart."
  • Play devil's advocate with your own solution
  • Try to break it with adversarial inputs
  • Find hidden assumptions
  • Look for edge cases
  • Refute your own solution before the interviewer does

Step 4: Good vs Bad

Bad

  • "This should work"
  • No edge-case thinking
  • Emotionally attached to first idea

Good

  • Actively searches for failures
  • Checks assumptions
  • Asks what input would break the logic
  • Plays devil's advocate against own solution

Edge-Case Questions to Ask

Input Boundaries

  • Empty input
  • Single element
  • Maximum size

Value Edge Cases

  • Duplicates
  • Negatives
  • Zeros

Assumption Checks

  • Sorted vs unsorted
  • Unique vs repeated
  • Overflow

Example of a Hidden Assumption

Bad assumption: "The input is sorted."

  • Was that guaranteed?

If not, a two-pointer idea may fail completely.

Sorted
1
2
3
4
5
Unsorted
3
1
4
2
5
05
Implement
"Now code. But code with discipline:"
  • Write in blocks
  • Use clear naming
  • Use helper functions when useful
  • Explain what each block does

Step 5: Good vs Bad

Bad

  • Giant block of code
  • Bad variable names
  • No structure
  • Hard to explain

Good

  • Clean naming
  • Helper functions where needed
  • Block-by-block implementation
  • Easy to explain

Ishyirwa mu Bikorwa Bibi

def f(a, t): d = {} for i in range(len(a)): x = t - a[i] if x in d: return [d[x], i] d[a[i]] = i
f?
a?
t?
d?
x?

Vague naming → harder to explain → lower readability → easier to confuse yourself

Ishyirwa mu Bikorwa Byiza

def two_sum(nums, target): value_to_index = {} for index, num in enumerate(nums): needed = target - num if needed in value_to_index: return [value_to_index[needed], index] value_to_index[num] = index
two_sum ✓
value_to_index ✓
needed ✓
index, num ✓

Readable → interviewer-friendly → easier to debug → easier to narrate

Helper Function Pattern: Grid Problems

Given a 2D grid, visit valid neighbors to compute or update a result.

def is_valid(row, col, rows, cols): return 0 <= row < rows and 0 <= col < cols
c

Direction Vector Pattern

directions = [ (-1, 0), # up (1, 0), # down (0, -1), # left (0, 1), # right ]
c
  • Less repeated code
  • Fewer mistakes
  • Cleaner traversal logic
06
Test Comprehensively
"Testing is not optional. Testing is part of the solution."
  • Sample case
  • Edge case
  • Tricky case
  • Boundary case

Step 6: Good vs Bad

Bad - "Random"

  • Tests only sample input
  • Tries random values
  • Stops after first success

Good - "Intentional"

  • Each test has a reason
  • Targets assumptions
  • Covers boundaries and duplicates

Two Sum: Testing Examples

[2, 7, 11, 15], target 9 basic case
[3, 3], target 6 duplicate values
[1, 2, 3, 4], target 7 answer at end
[5, 4], target 9 very small input
Does the logic still work when the same value appears twice?

What Interviewers Notice in Testing

When you test without being asked, you signal:

Discipline
Ownership
Maturity
Engineering Mindset
07
Simplify and Clean Up
"Once the code works:"
  • Improve names
  • Remove clutter
  • Simplify logic
  • Add minimal comments if useful

Step 7: Good vs Bad

Bad

  • Leaves confusing names
  • Keeps dead code
  • Makes no final pass

Good

  • Removes clutter
  • Improves clarity
  • Makes final explanation easier

Before and After Naming

Before
mp = {}
After
value_to_index = {}

Clarity is part of correctness.

The 3-Bullet Rule

3
What the idea is
Why it works
Why it is efficient enough

If you need 10 bullets, you do not understand it clearly yet.

The 40-Minute Interview Timeline

0-5 min
5-10
10-18
18-30
30-36
36-40
Understand
& restate
Solve
manually
Design &
complexity
Implement
Test
Clean up
& summarize

The Pivot Rule

If you spend 5-7 minutes and:

  • Cannot justify complexity
  • Keep breaking on edge cases
  • Cannot explain why it works
PIVOT
New data structure
New perspective
Simpler version first
Ask a focused hint

Good Hint Request vs Bad Hint Request

Bad
"I'm stuck."
Better
"I have an O(n²) solution, but I think the constraints need something faster. Should I be thinking about hashing or sorting?"

Why better: shows progress, shows awareness, asks a targeted question.

Communication Script You Can Reuse

  • "Let me restate the problem."
  • "I'll start with a small example."
  • "The brute force is O(n²), but I think we can do better."
  • "I'll implement this in two parts."
  • "Now I'll test edge cases."
II
Full Walkthrough: Image Smoother
"Let's apply all 7 steps to a second problem."

Problem: Image Smoother

You are given a 2D integer grid representing image pixel values. For each cell, compute the floor of the average of the valid neighboring cells around it, including the cell itself.
1
1
1
1
0
1
1
1
1

Center cell highlighted in teal, its neighbors shaded

Step 1: Understand - Image Smoother

  • Input: m × n integer matrix
  • Output: another m × n matrix
  • Use valid neighbors only (boundary-aware)
  • Include the center cell itself in the average
  • Average uses floor division
Corner: 3 neighbors + self = 4
c
n
×
n
n
×
×
×
×
Center: 8 neighbors + self = 9
n
n
n
n
c
n
n
n
n

Step 2: Manual Solve - Image Smoother

Compute the smoothed value for cell (0,0) by hand:

Input grid
1
1
1
1
0
1
1
1
1
Cell (0,0) = 1
Valid neighbors:
(0,1) = 1
(1,0) = 1
(1,1) = 0
Sum = 1 + 1 + 1 + 0 = 3
Count = 4
floor(3/4) = 0
Pattern: for each cell, gather valid neighbors, sum them, divide by count.

Step 3: Design - Image Smoother

Key Decisions

  • Use 9 direction offsets (including center)
  • Use is_valid bounds check
  • Allocate a fresh output matrix
  • Iterate every cell in the grid

Complexity

  • Each cell checks up to 9 neighbors
  • Total: O(m × n × 9) = O(m × n)
  • Space: O(m × n) for output
c

Step 4: Attack - Image Smoother

Boundary Questions

  • Corners have only 4 cells (including self)
  • Edges have only 6 cells
  • What about a 1×1 grid?

Value Questions

  • Is the center cell included in the average?
  • Floor division, not rounding
  • All same values → unchanged?

Implementation Traps

  • Modifying input in-place (corrupts later reads)
  • Off-by-one in bounds check
  • Integer vs float division
Key insight: we need a separate output matrix because reading and writing the same grid corrupts results.

Step 5: Implement - Image Smoother (Setup)

def image_smoother(img): rows, cols = len(img), len(img[0]) output = [[0] * cols for _ in range(rows)] neighbors = [ (-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 0), (0, 1), (1, -1), (1, 0), (1, 1), ]
-1,-1
-1,0
-1,1
0,-1
0,0
0,1
1,-1
1,0
1,1

Step 5: Implement - Full Solution

def image_smoother(img): rows, cols = len(img), len(img[0]) output = [[0] * cols for _ in range(rows)] neighbors = [ (-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 0), (0, 1), (1, -1), (1, 0), (1, 1), ] for row in range(rows): for col in range(cols): total = 0 count = 0 for dr, dc in neighbors: nr, nc = row + dr, col + dc if 0 <= nr < rows and 0 <= nc < cols: total += img[nr][nc] count += 1 output[row][col] = total // count return output

Step 6: Test - Image Smoother

[[1,1,1],[1,0,1],[1,1,1]] sample case - center cell surrounded
[[5,5,5],[5,5,5],[5,5,5]] all same values → output unchanged
[[7]] 1×1 grid → output is [[7]]
[[1,1,1],[1,0,1],[1,1,1]], cell (0,0) corner cell: (1+1+1+0)/4 = floor(3/4) = 0
Each test targets a specific boundary or assumption.

Step 7: Clean Up - Image Smoother

Before Cleanup

  • Variable name d for directions
  • Magic numbers like -1, 1 scattered
  • No comments on bounds check
  • In-place mutation risk

After Cleanup

  • neighbors is self-documenting
  • Direction offsets grouped visually
  • Separate output matrix is explicit
  • Code reads like the algorithm
Final check: Can you explain every line in one sentence?

Image Smoother: Good vs Bad

Bad

  • Starts coding loops immediately
  • Forgets output allocation
  • Forgets boundary checks
  • Repeats 9 conditions manually

Good

  • Identifies grid-neighbor problem
  • Uses neighbor offsets
  • Allocates output first
  • Handles bounds centrally

Ibyo Abakandida Bakomeye Bakora

Slow down at the start
Think before coding
Challenge their own ideas
Test intentionally
Communicate clearly
Recover calmly

Ibyo Abakandida Badakomeye Bakora

Rush into code
Ignore constraints
Hope the idea works
Skip edge cases
Panic when stuck
Never clean up

Kumvikana mbere.

Gukora neza kabiri.

Umuvuduko gatatu.

Speed without clarity creates wrong solutions.

The problem is not the problem. The problem is your attitude about the problem. Do you understand?
- Captain Jack Sparrow
1 / 56

Table of Contents