Roadmap › Low-Level Design (LLD) — Object Design, UML and Classic Problems in C# › Module: Classic LLD Problems
Lessons in this course (8)
7. Design an LRU cache and a rate limiter
Advanced★ Must knowLesson 7 of 8
LRU cache — O(1) get and put (dictionary + doubly linked list):
csharp
public sealed class LruCache<TKey, TValue>(int capacity) where TKey : notnull
{
private readonly Dictionary<TKey, LinkedListNode<(TKey Key, TValue Value)>> _map = [];
private readonly LinkedList<(TKey Key, TValue Value)> _order = new(); // front = most recent
private readonly Lock _lock = new();
public bool TryGet(TKey key, out TValue value)
{
lock (_lock)
{
if (_map.TryGetValue(key, out var node))
{
_order.Remove(node); _order.AddFirst(node); // mark as recently used
value = node.Value.Value; return true;
}
value = default!; return false;
}
}
public void Put(TKey key, TValue value)
{
lock (_lock)
{
if (_map.TryGetValue(key, out var existing)) _order.Remove(existing);
else if (_map.Count == capacity) { _map.Remove(_order.Last!.Value.Key); _order.RemoveLast(); } // evict least recent
_map[key] = _order.AddFirst((key, value));
}
}
}Token-bucket rate limiter per user:
csharp
public sealed class TokenBucket(int capacity, double refillPerSecond, TimeProvider clock)
{
private double _tokens = capacity;
private DateTimeOffset _last = clock.GetUtcNow();
private readonly Lock _lock = new();
public bool TryTake()
{
lock (_lock)
{
var now = clock.GetUtcNow();
_tokens = Math.Min(capacity, _tokens + (now - _last).TotalSeconds * refillPerSecond);
_last = now;
if (_tokens < 1) return false;
_tokens -= 1; return true;
}
}
}
public sealed class RateLimiter(Func<TokenBucket> create)
{
private readonly ConcurrentDictionary<string, TokenBucket> _buckets = new();
public bool Allow(string userId) => _buckets.GetOrAdd(userId, _ => create()).TryTake();
}In production use ASP.NET Core's built-in AddRateLimiter or Redis for limits shared across servers — but interviewers want to see that you can build it.