LeetCode Challenge Day 5 β 3508. Implement Router
Nitin Ahirwal / September 20, 2025
Hey folks
This is Day 5 of my LeetCode streak.
Todayβs problem is 3508. Implement Router β a system-design style problem where you must simulate a network router that stores packets in FIFO order with memory limits, supports duplicate detection, and allows time-window queries per destination.
Below I restate the problem, describe a clean, robust approach, give a step-by-step solution and the full JavaScript implementation I used, analyze time & space complexity, list edge-cases and testing ideas, and finally wrap up with some reflections.
π Problem Statement
We need to design a Router data structure that manages data packets with constraints on memory and operations. Each packet contains:
source: unique identifier of senderdestination: unique identifier of receivertimestamp: when the packet arrived
The router must support:
-
Initialization:
Router(memoryLimit)- Maximum number of packets the router can store.
- If adding exceeds limit, evict the oldest (FIFO).
-
Adding a packet:
addPacket(source, destination, timestamp)- Reject duplicate packets (
same source, destination, timestamp). - Return
trueif successfully added, elsefalse.
- Reject duplicate packets (
-
Forwarding a packet:
forwardPacket()- Return oldest packet
[source, destination, timestamp]in FIFO order. - Remove it from storage.
- If none left, return
[].
- Return oldest packet
-
Counting packets:
getCount(destination, startTime, endTime)- Count how many packets (not yet forwarded) match a
destinationand fall in the inclusive range[startTime, endTime].
- Count how many packets (not yet forwarded) match a
Constraints:
2 <= memoryLimit <= 1e5- At most
1e5operations total addPacketcalls always have non-decreasing timestamps
π‘ Intuition
Weβre basically simulating a network router with:
- FIFO storage (queue behavior).
- Duplicate prevention.
- Efficient queries for packets within a time window.
Key challenges:
- Evicting oldest efficiently.
- Checking duplicates quickly.
- Handling range queries fast (logarithmic ideally).
π Approach
1. Queue Simulation with Head Pointer
- Use an array
qand a head indexqHeadinstead of shifting. - Push new packets at the end, and move the head when forwarding/evicting.
- Gives
O(1)amortized for enqueue/dequeue.
2. Duplicate Check
- Maintain a
Setof packet keys (source#destination#timestamp). - Before inserting, check existence.
3. Per-Destination Storage for Range Queries
- Maintain a
Mapofdestination β [timestamps...]. - Since
addPacketis called in non-decreasing order of timestamps, arrays remain sorted. - Track a head index per destination (
perDestHead) so we can skip evicted packets logically.
4. Eviction Policy (when memory exceeds)
- Evict the oldest packet (from
qHead). - Update
seenset and per-destination timestamp head.
5. Range Query with Binary Search
- Use binary search (
bisectLeft,bisectRight) to find valid timestamps between[startTime, endTime]. - Complexity:
O(log n)for query.
β±οΈ Complexity Analysis
- Add packet:
O(1)amortized - Forward packet:
O(1) - Get count:
O(log n)due to binary search - Space:
O(memoryLimit)
π§βπ» Code (JavaScript)
var Router = function(memoryLimit) {
this.memoryLimit = memoryLimit;
this.q = [];
this.qHead = 0;
this.seen = new Set();
this.perDestTs = new Map();
this.perDestHead = new Map();
};
function makeKey(s, d, t) {
return s + '#' + d + '#' + t;
}
function bisectLeft(arr, target, lo = 0) {
let hi = arr.length;
while (lo < hi) {
const mid = (lo + hi) >>> 1;
if (arr[mid] < target) lo = mid + 1;
else hi = mid;
}
return lo;
}
function bisectRight(arr, target, lo = 0) {
let hi = arr.length;
while (lo < hi) {
const mid = (lo + hi) >>> 1;
if (arr[mid] <= target) lo = mid + 1;
else hi = mid;
}
return lo;
}
Router.prototype._queueSize = function() {
return this.q.length - this.qHead;
};
Router.prototype._evictOne = function() {
if (this._queueSize() === 0) return;
const [src, dest, ts] = this.q[this.qHead];
this.qHead++;
this.seen.delete(makeKey(src, dest, ts));
const arr = this.perDestTs.get(dest);
if (arr) {
const head = (this.perDestHead.get(dest) || 0) + 1;
if (head >= arr.length) {
this.perDestTs.set(dest, []);
this.perDestHead.set(dest, 0);
} else {
this.perDestHead.set(dest, head);
}
}
if (this.qHead > 1000 && this.qHead * 2 > this.q.length) {
this.q = this.q.slice(this.qHead);
this.qHead = 0;
}
};
Router.prototype.addPacket = function(source, destination, timestamp) {
const key = makeKey(source, destination, timestamp);
if (this.seen.has(key)) return false;
this.q.push([source, destination, timestamp]);
this.seen.add(key);
if (!this.perDestTs.has(destination)) {
this.perDestTs.set(destination, []);
this.perDestHead.set(destination, 0);
}
this.perDestTs.get(destination).push(timestamp);
while (this._queueSize() > this.memoryLimit) {
this._evictOne();
}
return true;
};
Router.prototype.forwardPacket = function() {
if (this._queueSize() === 0) return [];
const [src, dest, ts] = this.q[this.qHead];
this.qHead++;
this.seen.delete(makeKey(src, dest, ts));
const arr = this.perDestTs.get(dest);
if (arr) {
const head = (this.perDestHead.get(dest) || 0) + 1;
if (head >= arr.length) {
this.perDestTs.set(dest, []);
this.perDestHead.set(dest, 0);
} else {
this.perDestHead.set(dest, head);
}
}
if (this.qHead > 1000 && this.qHead * 2 > this.q.length) {
this.q = this.q.slice(this.qHead);
this.qHead = 0;
}
return [src, dest, ts];
};
Router.prototype.getCount = function(destination, startTime, endTime) {
const arr = this.perDestTs.get(destination) || [];
const head = this.perDestHead.get(destination) || 0;
if (head >= arr.length) return 0;
const left = bisectLeft(arr, startTime, head);
const right = bisectRight(arr, endTime, head);
return right - left;
};
β Example Walkthrough
Router router = new Router(3);
router.addPacket(1, 4, 90); // true
router.addPacket(2, 5, 90); // true
router.addPacket(1, 4, 90); // false (duplicate)
router.addPacket(3, 5, 95); // true
router.addPacket(4, 5, 105); // true, evicts [1,4,90]
router.forwardPacket(); // [2,5,90]
router.addPacket(5, 2, 110); // true
router.getCount(5, 100, 110); // 1
β±οΈ Complexity
addPacket: O(1) amortized
forwardPacket: O(1)
getCount: O(log n) per query
Space: O(memoryLimit)
π§ͺ Edge Cases & Testing
Duplicate packets β rejected correctly.
Memory overflow β oldest evicted.
Forwarding from empty queue β returns [].
Range query with no matches β returns 0.
Multiple destinations co-existing.
π₯ Video walkthrough
I recorded a short walkthrough explaining the idea and a step-by-step run of the example. Watch it here (or the embedded player below):
YouTube: https://youtu.be/LCFxbzLNUwc
π Conclusion
This problem was a nice blend of data structure design (queue + set + map) and algorithmic thinking (binary search for range queries). It reminded me of how real-world systems like network routers balance performance with constraints.
Thatβs it for Day 5! See you tomorrow for Day 6 π
Happy Coding! π¨βπ»