Latest
Day
053
Trapping Rain Water - water above every index
Per-index min(leftMax,rightMax)-height. Two pointers O(1) space. 2764x vs brute, 15.7x vs precompute at n=2K.
TodayILearned
A 365-entry fullstack engineering challenge. Entries are taught deeply and may be published in catch-up batches, so entry numbers are not calendar dates.
til.challenge
day 053 / 365
053
of 365 days
14.5% complete · 53 published · 35 with video
latest.entry
Day 053
Latest
Day
053
Per-index min(leftMax,rightMax)-height. Two pointers O(1) space. 2764x vs brute, 15.7x vs precompute at n=2K.
lesson.log
52 earlier lessons, newest first
GRANT leaks all tenants; RLS policies scope rows. Lab: open 100/100 vs RLS 40/60, cross INSERT 0 on til-postgres.
Multi-service checkout without 2PC: reverse compensations drive inconsistent terminals from ~46/200 to 0 in lab.
Blind read-modify-write loses concurrent decrements; version WHERE keeps stock correct with retries. Lab: final 99 vs 92 on til-postgres.
Atomic business write + outbox row; dual-write loses ~15% under crash; outbox loses 0 and may duplicate.
DB-native doorbell after COMMIT; not a durable queue. Lab: 30/30 commit deliver, 30/30 rollback silent, coalesce 30/30.
Converging two pointers maximize min(height)×width. Always advance the shorter side. 324.6x vs brute at n=2K.
Sort once, fix i, Day 40 squeeze for -nums[i], skip duplicates. O(n2). 236.9x vs brute at n=300.
Unbounded lock waits vs lock_timeout ceiling vs FOR UPDATE NOWAIT; lab medians 441 / 114 / 13.3 ms on til-postgres.
Fast producer, slow consumer. Unbounded max depth 3,750 vs cap 64 (58.6x). Block keeps all work; reject fails fast.
Leader election without a job row. pg_try_advisory_xact_lock. Lab: 8 false leaders vs 1 winner 30/30 on Podman PG 18.
Partition worker capacity so a slow dependency cannot starve healthy work. Lab: healthy 0→80 by t=40; wall 210→400 tradeoff.
RC permits non-repeatable reads (30/30); RR blocks them but write-skew 30/30; SSI blocks skew with serialization failures (retry).
Day 11 needed a hash map. Sorted input flips the tool: converging two pointers - O(n) time, O(1) space. 13.0x vs binary, 59.4x vs hash at n=100K.
Day 33 moved pointers in the same direction. Today they converge from both ends, skipping noise inline. No-alloc two-ptr: 6.3× faster than reverse at n=100K.
FOR UPDATE queues workers; SKIP LOCKED lets them claim different jobs. Lab: 40/0 vs ~18/22; 1.25× drain (1595→1271 ms).
Exponential backoff alone still clusters. Full jitter cuts client work ~4.21× (1275→303 attempts). Labeled multi-client sim.
Opposite lock order creates a wait-for cycle; PG aborts one victim. Lab: opposite 30/30 DL; ordered 0. Defense: sorted lock helper + 40P01 retry.
CLOSED to OPEN to HALF-OPEN: stop paying the timeout tax. Lab (fake clock): 10,200 ms to 450 ms (~22.67x wall), 200 to 5 dep calls (~40x). Pool-fill blast radius craft.
DEFERRABLE FKs let multi-statement txs temporarily break order; integrity still holds at COMMIT. Timing SVG + lab: cycle 1↔2 OK deferred; bulk child-first 3.467 vs parent-first 6.146 ms (0.564×).
Two pointers: walk t once, advance i only on match. Subsequence ≠ substring. Lab: indexOf ~97× vs two-pointer JS on |t|=500K.
Row / col / box Sets in one pass. Box index floor(r/3)*3+floor(c/3). Lab: bitmask 2.99× vs brute; Sets slower (alloc tax) on 20K boards.
EXCLUDE USING gist blocks multi-row conflicts (double-book). Lab PG 18.4: 2 rows/1 pair without; reject with; bulk 1.035×; range-race craft.
At-least-once delivery needs exactly-once effects. Lab: 6 attempts → 6 charges naive vs 1 with key; 50K unique 28.417 ms vs replay 1.716 ms (16.56×).
CHECK enforces domain predicates on every write. Lab PG 18.4: 2 bad rows without CHECK, 0 with; bulk tax 0.986× noise. Gate-slam craft.
Ring placement + virtual nodes: minimize remap on membership change. Lab: 3→4 modulo 75.29% vs consistent 20.71% (3.64× fewer moves); 10→11 10.61×. Vnode-spread craft.
Write-ahead log: append, fsync, then COMMIT; heap later. Lab PG 18.4: durable 3587.703 ms vs async 2392.958 ms (1.50×) vs UNLOGGED 2440.399 ms (1.47×). Crash-timeline craft.
Only expand from sequence heads (n-1 missing). Amortized O(n) with a Set - 124.7× faster than brute at n=5K mixed.
join("#") is not a codec. Prefix each string with its length, slice by that length on decode - 14.3× faster than escaping on hostile data.
UPDATE never overwrites a row. It creates a new version. Readers use snapshots. Dead versions pile up until VACUUM reclaims them - and restores Heap Fetches: 0.
Your database has one copy. If it goes down, everything goes down. Replication creates copies - but each copy introduces a new question: how stale is too stale?
Index only the rows you actually query. A partial index uses a WHERE clause to index a subset of rows - smaller, faster, and sometimes the only way to make a unique constraint make sense.
Your origin server is fast. But if the user is 10,000 km away, physics dominates. A CDN moves the cache to 300+ cities worldwide - the request never reaches your origin.
An index finds rows fast. A covering index returns the data too - PostgreSQL skips the table entirely. Heap Fetches: 0 is the magic line in EXPLAIN.
The no-division constraint forces a reframe: the answer for each position is the product of everything before it × everything after it. Two passes, O(n) time, O(1) auxiliary space.
Day 5 counted frequencies. Day 12 grouped anagrams by a canonical signature. Today: find the top K. Three approaches, three Big O complexities - and a benchmark that shows the textbook O(n) answer isn't always the fastest in practice.
Your API returns page 500 in 50ms. Page 5000 takes half a second. Page 10000 takes two seconds. The user is just clicking 'next page' - so why does each page get slower? The answer is OFFSET, and the fix is a cursor.
Load balancing distributes traffic. Rate limiting caps it. Caching skips work entirely. An API gateway does all three - plus authentication, routing, and protocol translation - at a single entry point. It's the composition layer where every previous lesson converges.
Normalization split your data into honest tables. JOINs stitch it back together - at a cost. The planner picks the algorithm, but you control the indexes that make it fast.
A load balancer distributes requests synchronously. A message queue decouples them asynchronously - the producer fires and forgets, the consumer processes at its own pace.
Three normal forms, three problems they solve. Normalization prevents anomalies; denormalization trades safety for speed.
Two ways to build a canonical key: sort each string (O(n × k log k)) or count characters (O(n × k)). The benchmark reveals a surprising crossover.
The #1 most-asked LeetCode problem. One pass, one hash map, one key insight: for each number, check if you've already seen its complement.
A connection pool gives you a connection. A transaction is what you do inside it - a unit of work that either fully succeeds or fully fails.
A load balancer distributes traffic. A rate limiter caps it. Here are the four algorithms and the tradeoff each one makes.
Every database connection is expensive. A pool keeps a few warm and reuses them - the load balancer's cousin, one layer deeper.
One server can't handle it all. Here's how you spread the load - and the tradeoffs of each strategy.
When you index multiple columns, order matters. Here's why - with a real benchmark.
When the question is 'how many times?', a hash map is the answer.
When 'have I seen this before?' is the question, a hash set is the answer.
How to see what the database is actually doing - and whether your index is being used.
And when does it bite you?
And the part nobody mentions: when it actually makes things worse.