anishfyi.com Upskill DSA Math System design

upskill / math / the numbers under the code

Interview math

Interviews use one page of math, forever. This is the page.

The complete math surface of a software interview: six sections, 22 modules, nothing academic. Every formula arrives attached to the question that uses it, so C(n,k) shows up holding Unique Paths and the latency table shows up holding a QPS estimate. Each module states the rule, then works a real example to the final digit. Check them off as they go cold; progress saves in your browser.

0 / 22

01Big-O

Growth rates, priced

A machine does about 108 to 109 simple operations per second. Count your algorithm's operations, divide, and you know whether it survives before you write a line. One table, three log facts, one honest way to price recursion: that is 80 percent of every complexity conversation you will ever have.

The gut-check table at n = 106

memorize the verdicts

When an interviewer says "n is up to a million", this table is the whole conversation. Run it in your head before proposing anything.

ClassNameOps at n = 106Verdict
O(1)constant1free at any n
O(log n)logarithmicabout 20free. binary search, tree hops
O(√n)square root1,000free. trial-division primality
O(n)linear106about a millisecond. one pass
O(n log n)linearithmic2 x 107fine. this is sorting
O(n2)quadratic1012dead: about 3 hours. fine only to n near 104
O(2n)exponentialunwritabledead: fine only to n near 25 (220 is 106)
O(n!)factorialbeyond astronomydead: fine only to n near 11 (11! is 4 x 107)

The three log facts

why log n keeps appearing
  • Products become sums. log(ab) = log a + log b, and log(ak) = k log a. This is why the number of digits of n is O(log n).
  • The base never matters. log2 n = log10 n / log10 2: any two bases differ by a constant factor, so Big-O never names one.
  • Halving is log. The number of times you can halve n before hitting 1 is log2 n, so binary search on 106 items takes about 20 probes, and on 109 about 30. A balanced tree of n nodes has height about log2 n for the same reason: doubling the data adds one hop.

Worked instance: heap sort is n pops, each pop repairs a tree of height log n, so n log n. Say it that way and the bound explains itself.

Amortized vs worst case

the doubling argument

Why is a dynamic array push O(1) when a resize copies everything? Price the whole sequence of n pushes, not the unlucky one.

capacities double: 1, 2, 4, ..., n
copies across all resizes = 1 + 2 + 4 + ... + n/2  <  n
add the n pushes themselves:  under 2n writes for n pushes  =  O(1) each, amortized

Hash map resize is the same trick. Average O(1) lookup additionally assumes the hash spreads keys; the worst case is every key in one bucket, O(n). Say "average, assuming a good hash function" and you sound careful because you are. One caveat worth volunteering: if a single slow operation is unacceptable (a latency-sensitive path), an amortized bound is the wrong tool.

Costs to have cold

StructureOperationAverageWorstNote
hash mapget, putO(1)O(n)resize amortized; worst is adversarial keys
balanced BSTget, put, min, rangeO(log n)O(log n)sorted iteration for free
binary heappush, popO(log n)O(log n)peek O(1); build from array O(n)
comparison sortn itemsO(n log n)O(n log n)provable floor; counting sort O(n + k) for small int keys
binary searchsorted arrayO(log n)O(log n)20 probes at 106, 30 at 109

Pricing recursion in plain words

branching ^ depth
cost of naive recursion  ≈  branching factor ^ depth
fib(n) branches 2 ways, n deep:  2^n
memoized: n distinct states x O(1) work each  =  O(n)
  • Halve, then linear work per level: n log n. Merge sort.
  • Halve, then constant work: log n. Binary search.
  • Halve, keep one side, linear work: n + n/2 + n/4 + ... = 2n = O(n). Quickselect on average.
  • Try every subset: 2n. Every ordering: n!.

The DP sentence that always lands: cost = number of states x work per state. n states with O(n) work each is O(n2), and you said it without a recurrence.

02Combinatorics

Counting without listing

Five tools count everything an interviewer will throw at you. The skill is picking the tool inside ten seconds: is order relevant, are repeats allowed, are the items distinct. Answer those three and the formula picks itself.

The counting toolkit

five formulas
You are countingFormulaWorked instance
orderings of all n distinct itemsn!seat 4 people in a row: 4! = 24
ordered pick of k from nn! / (n-k)!podium from 10 runners: 10 x 9 x 8 = 720
unordered pick of k from nC(n,k) = n! / (k!(n-k)!)poker hands: C(52,5) = 2,598,960
k slots, n options, repeats allowednk4-digit PINs: 104 = 10,000
k identical items into n bins (stars and bars)C(k+n-1, n-1)nonnegative x+y+z = 10: C(12,2) = 66

Two identities worth owning: C(n,k) = C(n, n-k) (choosing who is in equals choosing who is out), and Pascal's rule C(n,k) = C(n-1,k-1) + C(n-1,k), which is exactly the grid DP two modules down.

Inclusion-exclusion, and when Catalan appears

overlaps and nesting
|A or B| = |A| + |B| - |A and B|          # subtract what you counted twice
1..100 divisible by 3 or 5:  33 + 20 - 6 = 47

Three sets: add the singles, subtract the pairwise overlaps, add back the triple. The sign alternates; that is the whole theorem.

Catalan numbers: Cn = C(2n,n) / (n+1), running 1, 1, 2, 5, 14, 42. They count balanced, nested, non-crossing structures: valid parentheses strings with n pairs, distinct BST shapes on n nodes, lattice paths that never cross the diagonal. Sanity-check with n = 3, which should give 5:

((()))   (()())   (())()   ()(())   ()()()      # 5 strings = C_3

Interview signal: if the structure is balanced or non-crossing, guess Catalan and verify the n = 3 case by hand.

Drill: distinct paths in an m x n grid

worked drill

Unique Paths, straight off the Blind 75: a robot walks from the top-left of an m x n grid to the bottom-right, moving only right or down. Count the paths.

every path  =  (m-1) downs and (n-1) rights, in some order
            =  choose where the downs sit among m+n-2 moves
paths       =  C(m+n-2, m-1)
3 x 7 grid:    C(3+7-2, 3-1) = C(8,2) = 8*7/2 = 28

The DP everyone writes, dp[r][c] = dp[r-1][c] + dp[r][c-1], is Pascal's rule cell by cell: the grid is Pascal's triangle, sheared. Saying that out loud converts a coding question into a counting answer. Drill the DP form at /upskill/dsa/.

Drill: counting with a constraint

worked drill

From 6 engineers and 4 designers, how many 5-person teams have at least 2 designers?

sum the exact cases:
  exactly 2 designers:  C(4,2) * C(6,3)  =  6 * 20  =  120
  exactly 3 designers:  C(4,3) * C(6,2)  =  4 * 15  =   60
  exactly 4 designers:  C(4,4) * C(6,1)  =  1 * 6   =    6
  total                                             =  186

check by complement:
  all teams:            C(10,5)          =  252
  0 designers:          C(6,5)           =    6
  1 designer:           C(4,1) * C(6,4)  =   60
  252 - 6 - 60                           =  186   matches

"At least k" always means one of two routes: sum the exact cases, or subtract the shortfall from the total. When both fit in four lines, run both. Agreement is your unit test.

03Probability

Four rules, five classics

Interview probability is small: four rules and a handful of classics that repeat across companies for decades. Linearity of expectation is the one that feels like cheating: expectations add even when the variables are tangled together.

The rules, one line each

the whole theory
RuleStatementRead it as
conditionalP(A|B) = P(A and B) / P(B)probability, renormalized to the world where B happened
BayesP(A|B) = P(B|A) P(A) / P(B)flip the conditional; the prior P(A) does the heavy lifting
independenceP(A and B) = P(A) P(B)only when independent: coin flips and hash outputs yes, correlated failures no
linearityE[X + Y] = E[X] + E[Y]always true, dependent or not: the interview superpower
binomialmean np, spread √(np(1-p))100 fair flips: 50 heads, give or take 5
geometricE[trials to first success] = 1/prare events take 1/p attempts; the drills below are this rule

Bayes in the wild: a disease hits 1 in 10,000, the test is 99 percent accurate both ways. In a million people, 99 real positives drown in about 10,000 false ones, so a positive result is real about 1 time in 100. Base rates beat accuracy; say that sentence.

Linearity in action

three classics

Expected coin flips to the first heads. Flip once; tails puts you back where you started:

E = 1 + (1/2) * E      # one flip spent; heads ends it, tails restarts it
E = 2

Expected dice rolls to the first six. Geometric with p = 1/6, so E = 1/p = 6 rolls. Rare events cost 1/p attempts; this generalizes to retries, cache misses, and randomized algorithms.

Expected fixed points of a random permutation. Let Xi be 1 if position i keeps its element:

E[X_i] = 1/n            # each element is equally likely anywhere
E[X]   = n * (1/n) = 1  # the X_i are dependent; linearity does not care

The birthday bound

collisions arrive at √N

Among just 23 people, some pair shares a birthday with probability 50.7 percent. The general law: with N buckets, P(no collision among k items) is about e-k²/2N, so collisions become likely once k reaches about 1.2 √N.

N = 365:    1.2 * √365   ≈  1.2 * 19.1  ≈  23 people
N = 2^32:   1.2 * 2^16   ≈  79,000 items   # exact 50% point is near 77,000
N = 2^64:   1.2 * 2^32   ≈  5 x 10^9 items

Translation for hash maps and id generators: a 32-bit "unique" id collides within one busy afternoon of traffic. 64 bits moves the wall to about five billion. The same math prices rolling-hash collisions in section 05.

Reservoir sampling and Fisher-Yates

two classics

Reservoir sampling: keep a uniform sample of k from a stream of unknown length. Keep the first k items; when item i > k arrives, keep it with probability k/i, evicting a uniformly random resident. Why it is uniform, shown for k = 1:

P(item j survives to the end)
  = (1/j) * (j/(j+1)) * ((j+1)/(j+2)) * ... * ((n-1)/n)
  = 1/n                 # everything telescopes away; every item equally likely

Fisher-Yates shuffle, the correct form:

for i = n-1 down to 1:
    j = random integer in [0, i]      # NOT [0, n-1]
    swap a[i], a[j]

This makes n * (n-1) * ... * 1 = n! equally likely outcomes, one per permutation. The tempting bug, j = random in [0, n-1] every round, produces nn equally likely runs spread over n! permutations, and nn is not divisible by n! (for n = 3: 27 runs onto 6 permutations), so some permutations must come up more often. That divisibility argument is the entire interview answer.

04Bit manipulation

Seven tricks, five problems

Bits show up the moment the interviewer says O(1) space or without the + operator. Seven expressions cover it, and the five classics below are all on the Blind 75: drill their coded forms at /upskill/dsa/.

The tricks table

seven expressions
ExpressionEffectClassic use
x & (x-1)clears the lowest set bitpopcount loop; power-of-two test
x & -xisolates the lowest set bitFenwick trees; parity partitioning
x ^ x = 0,  x ^ 0 = xXOR cancels pairsSingle Number, Missing Number
x << 1,  x >> 1multiply, divide by 2fast exponentiation; mid = (lo+hi) >> 1
x >> k & 1reads bit kset x | (1<<k), clear x & ~(1<<k), flip x ^ (1<<k)
x != 0 && (x & (x-1)) == 0true iff x is a power of twoexactly one bit set
~x + 1 = -xtwo's complement negationwhy x & -x works at all

Parity in one line: popcount(x) & 1. And Kernighan's counter, while x: x &= x-1, loops once per set bit, not once per bit.

Single Number and Missing Number

xor everything

Single Number: every element appears twice except one. XOR the whole array; pairs cancel, order irrelevant, O(n) time, O(1) space.

[4,1,2,1,2]:   4 ^ 1 ^ 2 ^ 1 ^ 2  =  4

Missing Number: n distinct numbers drawn from 0..n, one missing. Two clean routes:

Gauss:  0..n sums to n(n+1)/2
        [3,0,1], n = 3:   expected 6, actual 4,  missing = 2
XOR:    (0^1^2^3) ^ (3^0^1) = 2       # every present value cancels twice

The Gauss sum deserves ten seconds of respect: pair 1 with n, 2 with n-1, and you have n/2 pairs each totalling n+1. That is why 1..100 is 5,050, and why "sum of first n integers" is O(1), never a loop.

Counting Bits, Reverse Bits, Sum of Two Integers

the trick, spelled out

Counting Bits: bit counts for every i from 0 to n, in O(n). Drop the last bit; you already counted the rest:

dp[i] = dp[i >> 1] + (i & 1)
i = 6 (110):  dp[6] = dp[3] + 0;  dp[3] = dp[1] + 1 = 2;  so dp[6] = 2

Reverse Bits: 32 rounds, peel from the right, build on the left:

repeat 32 times:   ans = (ans << 1) | (x & 1);   x >>= 1

Sum of Two Integers, no + allowed: XOR is the sum without carries, AND shifted left is exactly the carries. Loop until the carry dies:

while b != 0:   a, b  =  a ^ b,  (a & b) << 1
5 + 3:   (6, 2) -> (4, 4) -> (0, 8) -> (8, 0)      answer 8

05Modular arithmetic

Arithmetic under a prime

Any counting DP at scale ends with "return the answer mod 109+7". That constant is not folklore, it is engineering: four properties, one five-line function, and division comes back from the dead via Fermat.

Why 109+7, and the four properties

no plain divide

109+7 is prime, which unlocks modular inverses below, and it is small enough that the product of two reduced values, at most (109+6)2 ≈ 1018, still fits a signed 64-bit integer (max 9.2 x 1018). You can multiply then reduce without overflow. That pair of facts is the entire reason every judge uses it.

(a + b) mod m  =  (a mod m + b mod m) mod m
(a - b) mod m  =  (a mod m - b mod m + m) mod m     # the +m rescues negatives
(a * b) mod m  =  (a mod m * b mod m) mod m
a / b          =  not allowed. multiply by the inverse of b instead

Practical habit: reduce after every add and multiply inside the DP loop, and never subtract without the +m. Both bugs pass the small tests and fail the big ones.

Fast exponentiation and the inverse

log e squarings
power(a, e, m):
    r = 1;  a = a mod m
    while e > 0:
        if e is odd:  r = r * a mod m
        a = a * a mod m;  e = e >> 1
    return r

O(log e): an exponent of 1018 costs about 60 loop turns. Worked: 313 mod 1000, with 13 = 1101 in binary, takes four turns and returns 323 (313 = 1,594,323).

The inverse in one line, for prime m: inv(a) = power(a, m-2, m), by Fermat's little theorem. Payoff: C(n,k) mod p = n! * inv(k!) * inv((n-k)!) with factorials precomputed, which is section 02 running at n = 106.

Polynomial rolling hash

strings as numbers
h(s) = ( s[0]*b^(n-1) + s[1]*b^(n-2) + ... + s[n-1] ) mod m     # base b ≈ 31 or 131

slide the window in O(1), dropping s[i], adding s[i+n]:
h' = ( (h - s[i]*b^(n-1)) * b + s[i+n] ) mod m                  # precompute b^(n-1) mod m

This is the engine of Rabin-Karp substring search: compare hashes in O(1), expected O(n + m) overall. Why collisions are rare: two different strings collide with probability about 1/m ≈ 10-9 per comparison. But hash 105 strings against each other and you have about 5 x 109 pairs, so the birthday bound predicts a few collisions. The standard fix is two independent (base, mod) pairs, pushing odds to about 10-18.

06Estimation

Back of the envelope

This section feeds /upskill/system-design/ directly: every capacity question there spends the two tables and the recipe here. Memorize the tables, then run the drills until the multiplications are reflex.

The two tables to memorize

powers of 2, latency
PowerValueHook
2101,024about 103: a KB
21665,536port numbers; √(232)
220about 106a MB
230about 109a GB
2324,294,967,296about 4.3 billion: IPv4 space, uint32
240about 1012a TB
OperationCostHook
L1 cache hitabout 1 nsthe unit everything else is priced in
RAM referenceabout 100 ns100x L1
SSD random readabout 100 us1,000x RAM
read 1 MB from RAMabout 250 ussequential is the fast lane
round trip inside a datacenterabout 500 usasking another machine is not free
read 1 MB from SSDabout 1 ms4x RAM for the same MB
HDD seekabout 10 mswhy spinning disks lost
read 1 MB over a 1 Gbps linkabout 10 ms40x RAM for the same MB
cross-continent round tripabout 150 msphysics; a CDN is the only fix

These are the classic rounded figures; modern hardware beats some of them, but the ratios survive, and the ratios are the answer. Each step down the table costs roughly another factor of 100 to 1,000. Caching is just refusing to move down a row.

The back-of-envelope recipe

six steps, every time
  1. Fix the inputs out loud. DAU and actions per user per day, in round numbers. Invented round numbers beat silent precise ones.
  2. A day is 86,400 seconds; round to 105. The rounding runs about 14 percent low on average QPS; the 3x peak factor in the next step swallows that, and it makes every division trivial.
  3. Average QPS = events per day / 105. Peak is 2 to 5x average; say "I will take 3x" and move on.
  4. Storage = objects per day x size x retention days, then multiply by the replication factor, usually 3.
  5. Bandwidth = QPS x payload size.
  6. Land the sentence: "so we need N servers" or "so we need M TB". An estimate without a consequence is trivia.

Drill: size a URL shortener

worked drill

100M new links per day, reads outnumber writes 10 to 1, records about 500 B (short code, long URL, owner, timestamps).

write QPS:   10^8 / 10^5                      =  1,000 avg;  x3 peak  =  3,000
read QPS:    10 * 10^8  =  10^9 / 10^5        =  10,000 avg;  x3 peak  =  30,000

5-yr store:  10^8 links/day * 365 * 5         =  1.8 * 10^11 links
             * 500 B each                     =  9 * 10^13 B   =  about 90 TB

key space:   62^7 base-62 codes               ≈  3.5 * 10^12
             vs 1.8 * 10^11 needed            =  7 chars, about 19x headroom

cache:       hot 20% of 10^9 daily reads      =  2 * 10^8 requests
             * 500 B                          =  10^11 B  =  100 GB

Land it: 90 TB over five years is a small sharded database, 100 GB of cache is a few Redis boxes, and 30,000 read QPS is a load balancer over stateless app servers. One paragraph, every number defended.

Drill: size chat message fanout

worked drill

A chat app: 50M DAU, each sending 40 messages per day, messages about 100 B with metadata.

sends:       5 * 10^7 * 40                    =  2 * 10^9 msg/day
write QPS:   2 * 10^9 / 10^5                  =  20,000 avg;  x3 peak  =  60,000

storage:     2 * 10^9 * 100 B                 =  2 * 10^11 B  =  200 GB/day
             * 365                            =  73 TB/year raw
             * 3 replicas                     =  about 220 TB/year

sockets:     20% of 50M online at once        =  10^7 concurrent connections
             at 500k per gateway box          =  20 boxes;  run 30 for failover

The trap is fanout, not writes: 1-to-1 chat means deliveries roughly equal sends, another 20,000 QPS outbound. But one message to a 200-member group becomes 200 deliveries; at scale the delivery multiplier is what melts, so quote it separately. Land it: 60,000 peak write QPS partitions comfortably, 220 TB per year is a real but ordinary storage bill, and 30 gateway boxes hold the sockets.