Early Access: 87 spots left.

Claim
AI Coding InterviewAI Coding InterviewEvent Log Service: Fix, Filter, Tail

Problems

Event Log Service: Fix, Filter, Tail

Medium·Tagsai-coding-interview3-phasebug-fixdata-structures

Problem Statement

Three phases, roughly 60 minutes total. The codebase is a small append-only event log used by an audit-trail service. It has an Event value object, an `EventLog` aggregate, and a `LogQuery` helper. Phase 1 (Fix the bug, roughly 15 minutes). `EventLog.append(Event)` is supposed to insert the caller's event verbatim. A failing test shows that when two different events with timestamps 100 and 200 are appended, both come back with the same wall-clock timestamp. The bug is in `EventLog.append`. Find it and make the smallest possible fix. Phase 2 (Add a filter, roughly 20 minutes). Product wants `LogQuery.filterByType(EventLog log, String type)` to return every event whose `getType()` equals `type`, in original insertion order. The starter ships the method as a stub that throws. Implement it. An unknown type returns an empty list, not an exception. The match is case-sensitive. Phase 3 (Tail the log, roughly 25 minutes). Product wants `LogQuery.tail(EventLog log, int n)` to return the last `n` events, or fewer if the log has fewer. The naive O(L) scan-all-then-take-last is acceptable for the test. The operation should still be correct for L = 10,000 and n in the set {1, 100, 10000, 100000}. Edge cases: `n == 0` returns an empty list, `n` greater than log size returns everything, and a negative `n` throws `IllegalArgumentException`. The validator runs three scenarios. 1. phase1_append_preserves_timestamp: appending two events with timestamps 100 and 200 must produce two events whose `getTimestamp()` returns 100 and 200 respectively. The bug is that `append` overwrites the timestamp with `System.currentTimeMillis()`. 2. phase2_filter_by_type: `filterByType` returns matching events in insertion order, returns an empty list for unknown types and for an empty log, and uses case-sensitive matching. 3. phase3_tail: `tail(0)` returns []; `tail(L+1)` returns all L events; `tail(n)` returns the last n events in original order; `tail(-1)` throws `IllegalArgumentException`.

Examples

Example 1
Input
log.append(new Event("login", 100, "alice")); log.append(new Event("logout", 200, "alice")); log.getEvents().get(0).getTimestamp()
Output
"100"
Why
Phase 1 fix: append should preserve the caller's timestamp instead of overwriting it with System.currentTimeMillis().
Example 2
Input
LogQuery.filterByType(log, "login")
Output
"[Event(login, 100, alice)]"
Why
Phase 2: returns matching events in insertion order with case-sensitive matching.
Example 3
Input
LogQuery.tail(log, 3) on a 5-event log
Output
"the last 3 events in original (insertion) order"
Why
Phase 3: tail(n) returns the suffix in insertion order. It is not reversed.

Constraints

  • Phase 1: do not change Event or LogQuery. The bug is local to EventLog.append.
  • Phase 2: filterByType returns a new list. Do not mutate EventLog. Return an empty list for an unknown type or an empty log.
  • Phase 3: tail(0) returns []; tail(n > size) returns all events; tail(-1) throws IllegalArgumentException.
  • Returned lists must be in original (insertion) order. Do not reverse.
  • No external libraries. java.util and java.lang are the only allowed imports.

Hints

Stuck? Reveal a nudge toward the right pattern, one step at a time.

Hint 1
Phase 1 hint: open EventLog.append. The line that creates a new Event is the bug. It passes System.currentTimeMillis() instead of event.getTimestamp(). The one-line fix is to call events.add(event) directly.
Hint 2
Phase 2 hint: a for-loop over log.getEvents() with `if (e.getType().equals(type)) result.add(e)` is the simplest correct implementation. Use equals, not equalsIgnoreCase, because the contract is case-sensitive.
Hint 3
Phase 3 hint: validate n first (throw on negative). Then handle n == 0 by returning an empty list. Then n >= size by returning a copy of all events. Otherwise, return new ArrayList<>(events.subList(events.size() - n, events.size())). Wrapping in a new ArrayList detaches the result from the unmodifiable view.
Hint 4
Read the existing methods (getEvents() returns an unmodifiable view) before assuming you can return events.subList directly. The wrapping can bite you on later mutation.