Mock Week 5 — System Design and Coding Combined Session

Sanjeev SharmaSanjeev Sharma
6 min read

Advertisement

Overview

Senior-level FAANG interviews test both breadth and depth in the same session: design a system at high level, then implement one of its core components from scratch. This format appears at L5 at Google, E5 at Meta, and SDE II at Amazon. Week 5 builds the ability to transition fluidly between the two modes without losing context or communication quality.

Why This Matters

Most candidates prepare system design and coding as separate skills. Senior interviewers deliberately test the transition between them — they want to see that you can hold a high-level architectural decision in mind while simultaneously writing clean, correct code for a component that implements it.

FAANG mock interview preparation at week 5 requires practicing the handoff: "I described the URL shortener as using a base-62 counter — let me now implement that encoder." Candidates who can articulate the architecture and then implement a specific component without losing either context or code quality demonstrate the engineering judgment that earns senior-level ratings.

Session Format

PhaseTimeActivity
System design0:00 – 0:15High-level architecture, 3 components
Component deep dive0:15 – 0:25Drill into one specific component
Code the core0:25 – 0:55Implement the critical data structure or algorithm
Review and complexity0:55 – 1:00Walk one example, state exact O()

Core Framework — Three Combo Problems

Combo 1: URL Shortener — Base-62 Encoder

Design phase (15 minutes): encode URL, decode short code, handle 100 million URLs per day. Storage: key-value store such as DynamoDB. Encoding: base-62 counter or hash plus truncate. Collision handling: retry with increment. Read-to-write ratio is 100 to 1, so add a Redis read cache.

CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
 
def encode(n: int) -> str:
    res = []
    while n:
        res.append(CHARS[n % 62])
        n //= 62
    return ''.join(reversed(res)) or CHARS[0]
 
def decode(s: str) -> int:
    n = 0
    for c in s:
        n = n * 62 + CHARS.index(c)
    return n
const CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
 
function encode(n) {
  const res = [];
  while (n) { res.push(CHARS[n % 62]); n = Math.floor(n / 62); }
  return res.reverse().join('') || CHARS[0];
}
 
function decode(s) {
  let n = 0;
  for (const c of s) n = n * 62 + CHARS.indexOf(c);
  return n;
}

Time: O(log n base 62) for encode/decode | Space: O(log n) for result string

Combo 2: Distributed Cache — LRU Eviction

Design phase: consistent hashing distributes keys across nodes. LRU eviction runs within each node. Primary-replica replication for fault tolerance. Background TTL thread evicts expired keys.

The LRU implementation uses a doubly-linked list plus hash map for O(1) get and put:

class LRUNode:
    def __init__(self, key=0, val=0):
        self.key = key
        self.val = val
        self.prev = self.next = None
 
class LRUCache:
    def __init__(self, capacity: int):
        self.cap = capacity
        self.cache = {}
        self.head = LRUNode()   # dummy head
        self.tail = LRUNode()   # dummy tail
        self.head.next = self.tail
        self.tail.prev = self.head
 
    def _remove(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev
 
    def _insert_front(self, node):
        node.next = self.head.next
        node.prev = self.head
        self.head.next.prev = node
        self.head.next = node
 
    def get(self, key: int) -> int:
        if key not in self.cache:
            return -1
        self._remove(self.cache[key])
        self._insert_front(self.cache[key])
        return self.cache[key].val
 
    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            self._remove(self.cache[key])
        node = LRUNode(key, value)
        self.cache[key] = node
        self._insert_front(node)
        if len(self.cache) > self.cap:
            lru = self.tail.prev
            self._remove(lru)
            del self.cache[lru.key]

Time: O(1) for get and put | Space: O(capacity)

Combo 3: Real-Time Leaderboard — Top-K Players

Design phase: 10 million players, updates via Redis sorted set with ZADD at O(log n). Top-K query via ZREVRANGE at O(K log n). Write-behind pattern persists to a relational DB.

from sortedcontainers import SortedList
 
class Leaderboard:
    def __init__(self):
        self.scores = {}
        self.sl = SortedList(key=lambda x: -x[0])
 
    def update(self, player_id: str, score: int) -> None:
        if player_id in self.scores:
            self.sl.remove((self.scores[player_id], player_id))
        self.scores[player_id] = score
        self.sl.add((score, player_id))
 
    def top(self, k: int):
        return [(pid, sc) for sc, pid in self.sl[:k]]

Time: O(log n) per update, O(K) for top-K | Space: O(n) for all players

System Design Checklist

Before diving into any design, cover these five areas in order:

  1. Clarify requirements — functional (what does it do?) and non-functional (scale, latency, availability)
  2. Estimate scale — DAU, QPS, storage per day, bandwidth
  3. High-level design — Client, Load Balancer, App Servers, Cache, DB, replicas
  4. Deep dive one component — data model, indexing strategy, sharding key
  5. Trade-offs — SQL vs NoSQL, consistency vs availability, cache invalidation strategy

Common Mistakes

  • Jumping into code before completing the design phase — lose the architectural context
  • Not mentioning the read-to-write ratio — it determines caching strategy
  • Forgetting to handle the encode edge case when n equals 0
  • Skipping the LRU dummy head and tail — leads to null pointer bugs in remove
  • Not discussing the trade-off between fan-out on write vs fan-out on read

Interview Tips

  • Open the design phase with "Let me start with requirements — functional and non-functional" before drawing anything
  • Transition to code explicitly: "I described the encoder — let me implement it now"
  • During coding, reference the design: "This CHARS string matches what I said about base-62 encoding"
  • State the complexity of the implemented component in the context of the full system: "This is O(1) per get, which is what makes the cache worth building"
  • End with one trade-off you would revisit given more time

Key Takeaways

  • Week 5 mirrors the senior-level interview format: design breadth plus implementation depth in the same session
  • The handoff from design to code must be explicit — name the component you are implementing before typing
  • Base-62 encoding runs in O(log n base 62) time; URL shorteners typically use 6-7 character codes
  • LRU cache achieves O(1) get and put using a doubly-linked list plus hash map
  • A real-time leaderboard uses a sorted set for O(log n) updates and O(K) top-K queries
  • Always cover requirements, scale estimation, high-level design, deep dive, and trade-offs in every system design
  • The read-to-write ratio drives caching strategy — a 100-to-1 ratio requires aggressive read caching
  • Candidates who can design and implement without losing context demonstrate the judgment that earns senior ratings

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading