Thread-Safe LRU Cache with TTL
Reported by candidates from LinkedIn's online assessment. Pattern, common pitfall, and the honest play if you blank under the timer.
The mistake that sinks a first attempt on this LinkedIn OA, reported in September 2026, is treating it as a plain LRU cache and bolting expiry on at the end. The problem is a design question: a fixed-capacity LRU where every entry also dies at time + ttl. You process PUT and GET in order, return a string per operation, and the cache has to be consistent after each one. If you're taking this in the next day or two, learn the order of operations cold. StealthCoder sits invisibly on your screen as a safety net if you blank on the live OA.
The problem
Simulate a fixed-capacity least recently used cache whose entries also expire. You receive a capacity, parallel operation names, and integer argument rows. Process operations atomically in the supplied order. PUT uses [time,key,value,ttl]. It stores or replaces the key, which expires at time + ttl, and makes the key most recent. Produce "null". GET uses [time,key]. Produce the stored value as a decimal string and make the key most recent, or produce "-1" when absent. Times are nondecreasing. Before every operation, remove all keys whose expiration time is less than or equal to the current time. Remove expired keys before deciding whether a PUT needs LRU eviction. A non-positive capacity stores nothing. Return one output string for every operation. The sequence represents a legal linearization of concurrent calls, so each operation must observe and update one complete cache state. Function runLRUWithTTL(capacity: int, operations: String[], arguments: int[][]) → String[] Examples Example 1 capacity = 2 operations = ["PUT","PUT","GET","PUT","GET","GET"] arguments = [[0,1,10,5],[1,2,20,10],[2,1],[6,3,30,5],[6,1],[7,2]] return = ["null","null","10","null","-1","20"] Key 1 expires at time 5, so it is removed before the PUT at time 6. Key 2 remains live. Example 2 capacity = 1 operations = ["PUT","PUT","GET","PUT","GET"] arguments = [[0,5,7,3],[2,5,9,10],[4,5],[5,6,1,10],[6,5]] return = ["null","null","9","null","-1"] The second PUT replaces key 5 and extends its TTL. Inserting key 6 later evicts key 5 from the capacity-one cache. Constraints -100000 <= capacity <= 100000. 1 <= operations.length == arguments.length <= 100000. Each operation is exactly PUT or GET and has the stated argument count. -10^9 <= key, value <= 10^9. 0 <= time <= 10^9, times are nondecreasing, and 1 <= ttl <= 10^9. The sum time + ttl fits in a signed 64-bit integer.
Reported by candidates. Source: FastPrep
Pattern and pitfall
The trick is combining two structures: a hash map from key to node, and a doubly linked list ordered by recency. Add a min-heap or a second ordered structure keyed by expiration time. Before every operation, purge all keys with expiration <= current time. Because times are nondecreasing, that purge is amortized cheap. The pitfall is a PUT that replaces an existing key. The old expiration is now stale, so the heap holds a dead entry. Use lazy deletion: when you pop, check that the entry's expiration matches the key's current one. Also expire before deciding on eviction, or you'll evict a live key when an expired slot was free. Capacity <= 0 means return "null" and store nothing. A GET on a live key refreshes recency but not TTL. If you freeze on the stale-heap detail during the live OA, StealthCoder is the hedge that gets you a working solution.
Drill it cold or hedge it with StealthCoder. Either way, don't walk into the OA hoping you remember the trick.
You can drill Thread-Safe LRU Cache with TTL cold, or you can hedge it. StealthCoder runs invisibly during screen share and surfaces a working solution in under 2 seconds. The proctor sees the IDE. They don't see what's behind it. Made for the candidate who got the OA invite this morning and has 72 hours, not six months.
Get StealthCoderRelated leaked OAs
You've seen the question.
Make sure you actually pass LinkedIn's OA.
LinkedIn reuses patterns across OAs. Made for the candidate who got the OA invite this morning and has 72 hours, not six months. Works on HackerRank, CodeSignal, CoderPad, and Karat.
Thread-Safe LRU Cache with TTL FAQ
What's the core trick in the LinkedIn thread-safe LRU with TTL problem?+
Pair a hash map and a doubly linked list for O(1) LRU, then add a min-heap on expiration time. Purge expired keys before every operation. The concurrency wording just means each operation sees one complete state, so a sequential simulation is correct.
Do I need real locks or threads for this one?+
No. The statement says the sequence is a legal linearization, so operations are already ordered. You process them one at a time and return strings. Don't add synchronization. It only adds bugs and does nothing for the tests.
What edge cases break most first attempts?+
Expiring after the eviction check instead of before, forgetting that a replacing PUT changes the expiration, and not handling capacity of zero or negative. Also, expiry is inclusive: expiration <= current time means the key is gone. Example 1 checks this at time 6.
Does GET extend a key's TTL?+
No. GET makes the key most recent but leaves the expiration alone. Only PUT sets a new expiration, computed as time + ttl. Use 64-bit integers, since the sum can exceed 32 bits.
How do I prepare for this in 48 hours?+
Write a plain LRU from memory with a map and linked list, then add the heap with lazy deletion. Trace both examples by hand, especially the capacity-one replacement case. Then test expiry-at-exact-time and zero-capacity inputs. That covers nearly every failure mode.