Coding Interview Workshop · Lesson 23

Defend a bounded-cache policy with a senior rubric

A senior interview answer should name the resource budget, admission rule and failure behavior before choosing infrastructure.

Clarify the observable contract

A senior interview answer should name the resource budget, admission rule and failure behavior before choosing infrastructure. This policy tracks declared payload byte sizes and rejects updates that would exceed five accounted bytes. Replacing an existing key first subtracts its previous contribution. The program stores size metadata only; it does not allocate payloads or measure managed heap usage.

The counter is intentionally named accounted bytes because it sums caller-supplied sizes rather than observing allocations. A proposal uses a widened intermediate so subtracting the old size and adding the replacement cannot overflow int before comparison with the budget. A rejected proposal leaves both the size map and Used unchanged.

Walk through the verified program

Storing a at three consumes three. Adding b at three would require six and is rejected without mutation. Replacing a with two frees one accounted byte, so b at three then fits and Used becomes five. The sequence checks that a rejected proposal leaves the old state intact and that updates are not double-counted.

The rejected b insertion tests nonmutation under pressure, while the a replacement tests subtract-before-add accounting. Reading the output as payload allocation would be wrong because the program never stores byte arrays. It stores only keys and integer metadata, making the model's budget boundary especially explicit.

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

var cache=new ByteCache(5);
Console.WriteLine(cache.Put("a",3)); Console.WriteLine(cache.Put("b",3)); Console.WriteLine(cache.Put("a",2)); Console.WriteLine(cache.Put("b",3));
Console.WriteLine("accounted bytes:"+cache.Used);
sealed class ByteCache(int budget) {
    private readonly Dictionary<string,int> sizes=new(StringComparer.Ordinal); public int Used { get; private set; }
    public string Put(string key,int bytes) { if(budget<0 || bytes<0) return "invalid"; int old=sizes.GetValueOrDefault(key); long proposed=(long)Used-old+bytes; if(proposed>budget) return "rejected"; sizes[key]=bytes; Used=(int)proposed; return "stored"; }
}

Account for time, storage and allocations

Expected dictionary operations are constant-time, with O(u) metadata for u keys and key-text storage in addition to the stated payload budget. A zero-byte entry can still consume metadata, so a payload-byte limit alone does not bound entry count or actual process memory.

Metadata is O(u) even when every declared payload size is zero. An actual implementation could impose both entry and payload limits, account for key length and define whether duplicate updates allocate. Measuring managed heap bytes would require instrumentation and would still differ from total process resident memory.

Review assumptions and likely defects

Calling the counter a memory limit would overstate what it measures. It excludes keys, objects, allocator overhead and concurrency. A compound update also needs synchronization under shared access. The implementation chooses reject rather than eviction, expiration or persistence; those policies are deliberately left for the design discussion.

Adding eviction changes caller semantics: a successful new write may destroy another key. Adding TTL introduces a clock and expiry consistency. Adding multiple replicas creates separate local budgets unless another coordinator exists. Those decisions should appear as explicit alternatives in a senior discussion rather than a list of features presumed to come with a dictionary.

Practice one: discriminate edge cases

Test an oversized replacement of a and verify its previous size remains counted. Add many zero-byte distinct keys and explain why Used stays constant while metadata grows. Hint: a budget is meaningful only relative to the resource actually counted.

After rejecting an oversized replacement, submit an operation that fits only if the original size remained intact. Hint: inspect both the returned status and Used. Then add zero-byte distinct keys to demonstrate why a second entry-count budget is necessary for a meaningful bound on metadata growth.

Practice two: defend an extension

Design a rubric comparing entry bounds, payload bounds, eviction, TTL, consistency and operational complexity. Prioritize a measurable requirement, state assumptions and propose evidence for the chosen tradeoff rather than listing fashionable distributed components.

A practical first design can combine an entry cap, declared-payload cap and one synchronization boundary, choosing rejection before adding eviction. Measure real representative memory separately from the accounting policy. If TTL is required, define whether expired entries are removed on access or by maintenance, and test stale-entry effects on admission explicitly.

Evaluate correctness and interview reasoning

A senior rubric should reward stated assumptions, measurable limits, correctness invariants, failure behavior and evidence. It should not reward adding distributed components without a requirement they solve. The sample supports that discussion with an actual rejected update and a successful replacement under a five-byte declared budget. Its limits are equally concrete: metadata can grow through zero-byte keys, payloads are not stored and no concurrency is implemented. A good answer proposes the smallest additional policy needed to close a named gap and explains the cost of that policy instead of calling a counter a complete memory-management system.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

var cache=new ByteCache(5);
Console.WriteLine(cache.Put("a",3)); Console.WriteLine(cache.Put("b",3)); Console.WriteLine(cache.Put("a",2)); Console.WriteLine(cache.Put("b",3));
Console.WriteLine("accounted bytes:"+cache.Used);
sealed class ByteCache(int budget) {
    private readonly Dictionary<string,int> sizes=new(StringComparer.Ordinal); public int Used { get; private set; }
    public string Put(string key,int bytes) { if(budget<0 || bytes<0) return "invalid"; int old=sizes.GetValueOrDefault(key); long proposed=(long)Used-old+bytes; if(proposed>budget) return "rejected"; sizes[key]=bytes; Used=(int)proposed; return "stored"; }
}

Expected output

stored
rejected
stored
stored
accounted bytes:5
Try this
Test an oversized replacement of a and verify its previous size remains counted. Add many zero-byte distinct keys and explain why Used stays constant while metadata grows. Hint: a budget is meaningful only relative to the resource actually counted.

Further reading

Interview questions for this lesson