L4 Product SWE β€” Interview Plan

Google Β· Notion Β· San Francisco

6–8 weeks 1–2 hrs / day 5 days / week Python Product-focused SWE
To review: brush up on React / front-end fundamentals β€” interviewers may probe a mid-level SWE's front-end comfort (component lifecycle, state vs. props, hooks like useState/useEffect, re-render behavior, common performance pitfalls).
1

Base Building β€” Weeks 1–2

Python fluency Β· core data structures Β· behavioral foundations
Weekly Schedule
Day Session Time
MonPython syntax drills + 2 easy problems60 min
TueData structures deep dive (see problems below)75 min
WedWrite 5 STAR behavioral stories60 min
ThuArrays & strings pattern block75 min
FriReview solutions + flashcard pass45 min
Week 1 Problems β€” Arrays, HashMaps, Sets
#ProblemDiff
1Two SumEasyGoogle
217Contains DuplicateEasy
242Valid AnagramEasy
121Best Time to Buy and Sell StockEasyGoogle
49Group AnagramsMediumGoogle
238Product of Array Except SelfMediumGoogle
560Subarray Sum Equals KMediumGoogle
Week 2 Problems β€” Linked Lists, Stacks, Queues
Python Syntax Drills β€” Learn These Cold
Behavioral Bank β€” 5 STAR Stories to Write
Use "I", not "we". Quantify results where possible. Aim for 90-second verbal delivery each.
Phase 1 Exit Checklist
2

Build β€” Weeks 3–5

Medium problems under time pressure Β· system design foundations
Weekly Schedule
DaySessionTime
MonPattern block β€” 2 mediums, 25 min each75 min
TueSystem design deep dive (one topic, see curriculum)60 min
WedBehavioral practice out loud + pattern problems60 min
ThuPattern block β€” 2 more mediums75 min
FriWeak spot review + 1 easy warmup45 min
Week 3 Problems β€” Two Pointers Β· Sliding Window Β· Binary Search
Week 4 Problems β€” Trees Β· Graphs Β· Backtracking
#ProblemDiff
104Maximum Depth of Binary TreeEasy
102Binary Tree Level Order TraversalMediumGoogle
199Binary Tree Right Side ViewMediumGoogle
98Validate Binary Search TreeMediumGoogle
236Lowest Common Ancestor of a Binary TreeMediumGoogle
200Number of IslandsMediumGoogle
133Clone GraphMedium
207Course ScheduleMediumGoogle
417Pacific Atlantic Water FlowMedium
78SubsetsMedium
46PermutationsMediumGoogle
39Combination SumMedium
79Word SearchMediumGoogle
Week 5 Problems β€” Dynamic Programming Β· Heaps
#ProblemDiff
70Climbing StairsEasy
198House RobberMedium
322Coin ChangeMediumGoogle
91Decode WaysMediumGoogle
139Word BreakMediumGoogle
62Unique PathsMedium
1143Longest Common SubsequenceMedium
215Kth Largest Element in an ArrayMediumGoogle
347Top K Frequent ElementsMedium
621Task SchedulerMediumGoogle
295Find Median from Data StreamHardGoogle
System Design Curriculum
Frontend system design β€” these come up at product-focused companies. Be ready to discuss:
  • How would you design a component library from scratch?
  • Client-side routing vs SSR β€” when to use which?
  • How do you handle state in a large React app?
  • What happens between typing a URL and seeing a page?
  • How would you optimize a slow React page?
Phase 2 Exit Checklist
3

Peak β€” Weeks 6–7

Full mock interviews Β· hard problems Β· company-specific prep
Weekly Schedule
DaySessionTime
MonFull mock coding interview β€” no hints, no pausing60 min
TueSystem design full mock (45 min) + debrief60 min
WedHard problem β€” 45 min attempt, then study solution75 min
ThuFull mock coding interview60 min
FriBehavioral mock + company research60 min
Hard Problems β€” At Least One Per Week
#ProblemDiff
42Trapping Rain WaterHardGoogle
23Merge K Sorted ListsHardGoogle
297Serialize and Deserialize Binary TreeHardGoogle
127Word LadderHardGoogle
146LRU CacheMediumGoogle
Company-Specific Prep

Google

Notion

Mock Interview Protocol
Best mock resources: interviewing.io (real engineers, free tier), Pramp (peer mocks, free), LeetCode weekly contest (time pressure simulation).
Phase 3 Exit Checklist
4

Taper β€” Week 8

Trust your prep β€” sharpen, don't cram
Race Week Checklist

Coding Interview Approach
#StepNotes
1 Clarify the problem Restate it in your own words. Let the interviewer correct misunderstandings early before you've written any code.
2 Ask about constraints and edge cases Input bounds, nulls, empty inputs, duplicates, negatives. Shows defensive thinking. Ask explicitly: "Can the input be empty?" "Can values be negative?"
3 Talk through your approach before coding Most skipped step. Start with brute force, then optimize. Get the interviewer to confirm your direction before touching code β€” you might be heading somewhere wrong, or there's a better approach they're looking for.
4 Write the code Talk through what you're doing as you go. Silence is a red flag. If stuck on a detail, say so and move on β€” come back to it.
5 Test your code Trace through test cases on the code you wrote, not the algorithm in your head. This catches off-by-one errors and missed edge cases that feel correct mentally but aren't in the actual code.
6 Discuss complexity Time and space, and explain why. O(n) isn't enough if it's actually O(n log n). Be precise.
Step 3 is where most candidates fail. Jumping straight to code without alignment is a red flag β€” you might solve the wrong problem, or miss an optimization the interviewer was looking for.
Target Companies
CompanyNotesApply
GoogleRecruiter outreach or referral strongly preferredWeek 6
NotionApply direct + find an employee referralWeek 6
LinearSmaller team, moves fast, strong eng cultureWeek 6
FigmaHeavy frontend, design-system workWeek 6
VercelFull-stack, strong eng cultureWeek 7
StripeHigh bar, very well-run processWeek 7
AnthropicIf open to AI product workWeek 7
LeetCode Premium β€” Get It
Python Patterns Cheatsheet

Core Data Structures

# Lists
arr = [1, 2, 3]
arr.append(4)
last = arr.pop()          # removes and returns last
arr[0]                    # first element
arr[-1]                   # last element
arr[1:3]                  # slice: index 1 inclusive to 3 exclusive
arr[::-1]                 # reversed copy
len(arr)

# Sets β€” O(1) average membership
s = {1, 2, 3}
empty_set = set()
s.add(4)
s.discard(2)              # no error if missing; .remove() raises KeyError
if 3 in s: ...
unique_vals = set(arr)

# Dicts
mp = {'a': 1, 'b': 2}
mp['c'] = 3
val = mp.get('d', 0)      # 0 if 'd' not in mp β€” no KeyError
if 'a' in mp: ...
del mp['a']

# Tuples β€” immutable; hashable (can be dict keys or set members)
t = (1, 2, 3)
t[0]
list(t)                   # convert to list
tuple([1, 2, 3])          # convert from list

Iteration Patterns

# Arrays β€” prefer enumerate over range(len(...))
for x in arr: ...
for i in range(len(arr)): ...
for i, x in enumerate(arr): ...      # index + value together
for x in reversed(arr): ...

# range(start, stop, step) β€” stop is exclusive
range(5, 10)                          # 5, 6, 7, 8, 9
range(0, 10, 2)                       # 0, 2, 4, 6, 8
range(10, 0, -1)                      # 10, 9, ..., 1 β€” count down

# Dicts
for k in mp: ...                      # keys
for v in mp.values(): ...
for k, v in mp.items(): ...           # key + value (most common)

# Sets β€” order not guaranteed
for x in s: ...
for x in sorted(s): ...              # if you need a stable order

2D Grids

# Initialize β€” m rows of n columns
rows, cols = 3, 4
grid = [[0] * cols for _ in range(rows)]   # CORRECT β€” independent rows
# bad_grid = [[0] * cols] * rows           # WRONG β€” all rows are the same object

# Iterate by index
for r in range(rows):
    for c in range(cols):
        val = grid[r][c]

# Iterate with enumerate (index + value)
for r, row in enumerate(grid):
    for c, val in enumerate(row):
        print(r, c, val)

# 4-direction deltas (up, down, left, right)
dirs_4 = [(-1, 0), (1, 0), (0, -1), (0, 1)]

# 8-direction deltas (including diagonals)
dirs_8 = [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(-1,1),(1,-1),(1,1)]

# Bounds check helper
rows, cols = len(grid), len(grid[0])
in_bounds = lambda r, c: 0 <= r < rows and 0 <= c < cols

for dr, dc in dirs_4:
    nr, nc = r + dr, c + dc
    if in_bounds(nr, nc):
        ...   # safe to access grid[nr][nc]

Algorithm Templates

# Two pointers
l, r = 0, len(arr) - 1

# Sliding window
from collections import defaultdict
window = defaultdict(int)
l = 0
for r in range(len(s)):
    window[s[r]] += 1
    while invalid(window):
        window[s[l]] -= 1
        l += 1

# BFS β€” graph
from collections import deque
q = deque([start])
visited = {start}
while q:
    node = q.popleft()
    for nei in graph[node]:
        if nei not in visited:
            visited.add(nei)
            q.append(nei)

# BFS β€” 2D grid
from collections import deque
def bfs_grid(grid, start_r, start_c):
    rows, cols = len(grid), len(grid[0])
    dirs = [(-1,0),(1,0),(0,-1),(0,1)]
    in_bounds = lambda r, c: 0 <= r < rows and 0 <= c < cols
    q = deque([(start_r, start_c)])
    visited = {(start_r, start_c)}
    while q:
        r, c = q.popleft()
        for dr, dc in dirs:
            nr, nc = r + dr, c + dc
            if in_bounds(nr, nc) and (nr, nc) not in visited:
                visited.add((nr, nc))
                q.append((nr, nc))

# DFS β€” iterative
stack = [start]
visited = {start}
while stack:
    node = stack.pop()
    for nei in graph[node]:
        if nei not in visited:
            visited.add(nei)
            stack.append(nei)

# Binary search β€” lo/hi loop
def binary_search(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

Common Gotchas

# 1. Mutable default arguments β€” shared across all calls
def f_bad(x, arr=[]):      # BAD
    arr.append(x); return arr
def f_good(x, arr=None):   # GOOD
    if arr is None: arr = []
    arr.append(x); return arr

# 2. is vs == 
a, b = [1, 2], [1, 2]
a == b   # True  β€” same value
a is b   # False β€” different objects; use `is` only for None checks
if x is None: ...

# 3. Integer vs float division
5 / 2    # 2.5  (float)
5 // 2   # 2    (floor); use for index math: mid = (lo + hi) // 2

# 4. sort() vs sorted() β€” sort() mutates and returns None
arr.sort()              # in-place, returns None
new_arr = sorted(arr)   # returns new list
arr = arr.sort()        # BUG β€” arr is now None

# 5. Modifying a list while iterating over it
for x in arr:
    if condition(x): arr.remove(x)        # BAD β€” skips elements
new_arr = [x for x in arr if not condition(x)]  # GOOD

# 6. range() is end-exclusive
range(n)              # 0 .. n-1
range(start, end)     # start .. end-1
for i in range(1, n + 1): ...   # inclusive 1..n

# 7. any() / all()
if any(x > 0 for x in arr): ...   # at least one satisfies
if all(x >= 0 for x in arr): ...  # every element satisfies
Python Syntax Drills β€” Code Examples & Practice Problems

collections.defaultdict

# Never KeyError β€” missing keys auto-initialize
graph = defaultdict(list)
graph['a'].append('b')        # no need to check if 'a' exists first

freq = defaultdict(int)
for c in "banana":
    freq[c] += 1              # starts at 0 automatically
#ProblemDiff
49Group AnagramsMedium
207Course ScheduleMedium
133Clone GraphMedium

collections.Counter

from collections import Counter

c = Counter("banana")         # Counter({'a': 3, 'n': 2, 'b': 1})
c.most_common(2)              # [('a', 3), ('n', 2)]
c['z']                        # 0 β€” no KeyError

# Subtract two counters
c1 = Counter("abc")
c2 = Counter("ab")
c1 - c2                       # Counter({'c': 1})
#ProblemDiff
242Valid AnagramEasy
347Top K Frequent ElementsMedium
76Minimum Window SubstringHard

collections.deque

from collections import deque

dq = deque([1, 2, 3])
dq.appendleft(0)    # O(1) β€” unlike list.insert(0, x)
dq.popleft()        # O(1) β€” unlike list.pop(0)

# BFS template
queue = deque([start])
while queue:
    node = queue.popleft()
    for neighbor in graph[node]:
        queue.append(neighbor)
#ProblemDiff
102Binary Tree Level Order TraversalMedium
127Word LadderHard
239Sliding Window MaximumHard

heapq

import heapq

h = []
heapq.heappush(h, 3)
heapq.heappop(h)          # returns smallest

# Max heap β€” negate values
heapq.heappush(h, -5)
-heapq.heappop(h)         # returns 5

heapq.heapify(nums)       # in-place, O(n)
heapq.nlargest(3, nums)   # [5, 4, 3]
#ProblemDiff
215Kth Largest Element in an ArrayMedium
23Merge K Sorted ListsHard
295Find Median from Data StreamHard
621Task SchedulerMedium

List Comprehensions Β· enumerate Β· zip

# List comprehension
squares = [x**2 for x in range(10) if x % 2 == 0]

# Grid init β€” DO NOT do [[0]*n]*m (aliasing bug!)
grid = [[0] * n for _ in range(m)]

# enumerate β€” index + value together
for i, val in enumerate(nums):
    print(i, val)

# zip β€” pair two lists, or transpose a matrix
for a, b in zip(list1, list2): ...
transposed = list(zip(*matrix))   # [(1,4),(2,5),(3,6)]
#ProblemDiff
48Rotate ImageMedium
54Spiral MatrixMedium
200Number of IslandsMedium

sorted() with key=

# Sort by second element
sorted(pairs, key=lambda x: x[1])

# Multi-key sort
sorted(words, key=lambda w: (len(w), w))

# Descending
sorted(nums, reverse=True)

# In-place
intervals.sort(key=lambda x: x[0])  # sort by start
#ProblemDiff
56Merge IntervalsMedium
435Non-overlapping IntervalsMedium
253Meeting Rooms IIMedium

dict.get(key, default)

d = {"a": 1}
d.get("b", 0)              # 0, no KeyError

# One-liner frequency count
freq = {}
freq[c] = freq.get(c, 0) + 1
#ProblemDiff
1Two SumEasy
560Subarray Sum Equals KMedium
146LRU CacheMedium

String Methods

s = "  hello world  "
s.strip()               # "hello world"
s.split()               # ["hello", "world"]
" ".join(["a","b","c"]) # "a b c"
s[::-1]                 # reverse

"abc".isalpha()         # True
"123".isdigit()         # True
"abc123".isalnum()      # True
#ProblemDiff
125Valid PalindromeEasy
151Reverse Words in a StringMedium
5Longest Palindromic SubstringMedium

set Operations

a = {1, 2, 3}
b = {2, 3, 4}

a | b    # union       {1,2,3,4}
a & b    # intersect   {2,3}
a - b    # difference  {1}

# O(1) lookup β€” prefer over list for membership checks
seen = set()
if x not in seen:
    seen.add(x)
#ProblemDiff
217Contains DuplicateEasy
128Longest Consecutive SequenceMedium
141Linked List CycleEasy

bisect

import bisect

nums = [1, 3, 4, 7, 9]

bisect.bisect_left(nums, 4)    # 2 β€” first index >= 4
bisect.bisect_right(nums, 4)   # 3 β€” first index > 4

bisect.insort(nums, 5)         # insert, keeping sorted β€” O(n)

# Check membership in O(log n)
idx = bisect.bisect_left(nums, x)
found = idx < len(nums) and nums[idx] == x
#ProblemDiff
981Time Based Key-Value StoreMedium
300Longest Increasing SubsequenceMedium
315Count of Smaller Numbers After SelfHard
Quick signal β†’ tool lookup
"top K" / "kth largest"heapq
BFS / shortest pathdeque
frequency / anagramCounter
graph adjacency / groupingdefaultdict(list)
sorted input + O(log n) searchbisect
membership / dedupset
sort by custom rulesorted(key=...)