# Map and set getters to give a way to get an item at an order

**URL:** <https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541>\
**Category:** 💡 Ideas\
**Created:** [April 16, 2026, 9:11am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541 "2026-04-16T09:11:50Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 16, 2026, 9:11am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/1 "2026-04-16T09:11:50Z")

</div>

# Map.prototype.at() & Set.prototype.at()

**Stage:** 0 (with a dream)  
**Author:** Someone tired of converting Maps to arrays  
**Help wanted:** Champion, spec writers, emotional support

## The Pitch

Arrays and strings got `.at()` in ES2022. Negative indices. Clean syntax. No more `arr[arr.length - 1]` nonsense.

Maps and Sets are ordered too. They deserve the same dignity.

## The Problem (with evidence)

```js
const users = new Map([
  [1, 'Alice'],
  [2, 'Bob'], 
  [3, 'Charlie']
]);

// Current state of affairs (all terrible):
const last = [...users.values()].pop(); // O(n) memory + time
const lastAgain = Array.from(users).at(-1)[1]; // still O(n) memory
let lastManual;
for (const v of users.values()) lastManual = v; // O(n) time, but gross

// Meanwhile, in array-land:
const arr = ['Alice', 'Bob', 'Charlie'];
const elegant = arr.at(-1); // 'Charlie' — chef's kiss

```

Search data: "how to get last element of Map" has too many results for something this basic.

The Solution

```js
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
const set = new Set(['red', 'green', 'blue']);

// Happy path
map.at(0); // 1 (first value)
map.at(-1); // 3 (last value — finally)
set.at(0); // 'red'
set.at(-1); // 'blue'

// Edge cases
map.at(99); // undefined (no crash, just vibes)
map.at(-99); // undefined
map.at('a'); // undefined (type checking)

```

## Basic API usage

```js
// Primary: just values (consistent with array/string)
Map.prototype.at(index) // returns value
Set.prototype.at(index) // returns value

// Secondary: when you need the key too
Map.prototype.entryAt(index) // returns [key, value] or undefined

```

## Why this is better than the boolean flag:

•No polymorphic return types (TypeScript/type systems are happier)  
•Clearer intent: map.entryAt(-1) vs map.at(-1, true)  
•Easier to polyfill and remember  
•Memory & Performance

| Method | Memory | Time | Clean Code |
| --- | --- | --- | --- |
| `[...map.values()].pop()` | O(n) | O(n) | no |
| `Array.from(map).at(-1)` | O(n) | O(n) | god no |
| Manual iteration | O(1) | O(n) | NO |
| `map.at(-1)` | O(1) | O(n) | yes |
| Same O(n) time. Zero O(n) memory. The array copy was the real crime. | | | |

## Real-World Usage

```js
// Get last 5 notifications without O(n) memory explosion
const notifications = new Map(); // id -> notification
// ... thousands of notifications ...

const lastFive = [];
for (let i = -1; i >= -5 && notifications.at(i); i--) {
  lastFive.unshift(notifications.at(i));
}

// Dashboard: first and last metrics
const metrics = new Map([['cpu', 42], ['memory', 86], ['disk', 33]]);
metrics.at(0); // 42 (current CPU)
metrics.at(-1); // 33 (disk usage)

// Pagination with Sets (unique active users)
const activeUsers = new Set(['alice', 'bob', 'charlie']);
const pageOne = [activeUsers.at(0), activeUsers.at(1), activeUsers.at(2)];

```

The Polyfill (production-ready)

```js
// Map
if (!Map.prototype.at) {
  Map.prototype.at = function(index) {
    if (typeof index !== 'number') return undefined;
    
    let target = index >= 0 ? index : this.size + index;
    if (target < 0 || target >= this.size) return undefined;
    
    let i = 0;
    for (const value of this.values()) {
      if (i++ === target) return value;
    }
  };
}

if (!Map.prototype.entryAt) {
  Map.prototype.entryAt = function(index) {
    if (typeof index !== 'number') return undefined;
    
    let target = index >= 0 ? index : this.size + index;
    if (target < 0 || target >= this.size) return undefined;
    
    let i = 0;
    for (const entry of this) {
      if (i++ === target) return entry;
    }
  };
}

// Set
if (!Set.prototype.at) {
  Set.prototype.at = function(index) {
    if (typeof index !== 'number') return undefined;
    
    let target = index >= 0 ? index : this.size + index;
    if (target < 0 || target >= this.size) return undefined;
    
    let i = 0;
    for (const value of this) {
      if (i++ === target) return value;
    }
  };
}

```

## FAQ (anticipating TC39 objections)

**Q: Isn't O(n) bad?**  
A: Maps aren't arrays. If you need O(1) indexed access, use an array. This is for occasional "give me the first/last/nth item" without allocating a temporary array.

**Q: Why not just use Map.prototype.first and Map.prototype.last?**  
A: Because at(-2) exists. Because symmetry with arrays matters. Because developers already know .at().

**Q: What about numeric keys?**  
A: .at() uses insertion order, not key lookup. map.get(3) is for keys. This is consistent with array behavior.

**Q: This is just a utility function, not a language feature**  
A: So was Array.prototype.at before it landed. Sometimes the little quality-of-life wins matter most.

**Q: Will this be used enough to justify spec time?**  
A: Every project I've worked on has a lastMapValue utility or inline [...map.values()].pop(). The array conversion guilt trip is real and widespread.

## Why This Should Move Forward

Symmetry - Arrays and strings have it. Maps and Sets are ordered iterables.  
Discoverability - .at() is already in every JS developer's mental model.  
Memory safety - Eliminates unnecessary array allocations.  
Developer happiness - Small wins add up.

## The TL;DR

Arrays got nice things. Maps and Sets are ordered too. Give them .at() so I can stop explaining to juniors why map.get(map.size - 1) doesn't work and why [...map.values()].pop() is embarrassing.

Thank you for coming to my TC39 talk.

---

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 16, 2026, 9:12am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/2 "2026-04-16T09:12:52Z")

</div>

Also, sorry for the recent amount of posts Ive made in one day, I have been working on this single function and accidentally posted 2 other wrong plan files :/  
sincerely sorry to the servers  
By the way any questions asked in the comments will have to wait some time before they get answered by me because I'm busy with programming (a lot)

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [April 16, 2026, 11:17am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/3 "2026-04-16T11:17:40Z")

</div>

Related thread:

> [@Method to access the \`nth\` inserted element in a map](https://es.discourse.group/t/method-to-access-the-nth-inserted-element-in-a-map/1925):
>
> For example: const map = new Map(); map.set(1, 2); map.set(3, 4); console.log(map.nth(1)); // [3, 4] Preserved insertion order is a handy feature of Maps. It makes them more useful for building caches with eviction policies. For example, you can build an LRU cache by deleting and re-inserting items when they are accessed. While implementing the SIEVE algorithm in JS, I noticed that an .nth() method would be handy. Without it, I need to track insertion order in a seperate array, OR copy the ke…

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [April 16, 2026, 11:33pm UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/5 "2026-04-16T23:33:34Z")

</div>

`mapOrSet.values().drop(n).take(1).toArray()[0]`?

---

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 16, 2026, 11:59pm UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/6 "2026-04-16T23:59:41Z")

</div>

That works, but it creates two intermediate iterators and an array for a single value. `map.at(n)` is O(n) with no temporary allocations. Your example allocates at least two iterators and an array — fine for small Maps, but for large ones (hundreds of thousands of entries), that's measurable memory pressure. Plus, `drop(n)` still iterates through all previous entries — same time complexity, worse memory profile.

---

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 17, 2026, 12:00am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/7 "2026-04-17T00:00:58Z")

</div>

Thanks for the real-world example! The SIEVE algorithm is exactly the kind of use case where `map.entryAt(n)` shines — avoiding a separate tracking array saves memory.\*

_To address your feasibility question: Map insertion order is tracked internally via a linked list of entries (or similar structure in engines like V8). The order is already there; we're just asking for read-only indexed access to that linked list. Engines can implement this by walking the list O(n) — same as `[...map.keys()][n]` but without allocating the intermediate array._

\*So your `.nth()` example is entirely feasible today. Engines like V8 could optimize it further for repeated access patterns, but even a naive O(n) implementation would match what developers currently do — just with less memory pressure.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [April 17, 2026, 12:14am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/8 "2026-04-17T00:14:22Z")

</div>

There's no requirement for engines to implement Maps or Sets so that it's actually O(1) for index access - theoretical big O isn't necessarily relevant. The only thing that's required to be nonlinear is _key_ access.

What's your use case where you have large Maps and you need to access by index, but can't by key?

---

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 17, 2026, 5:56am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/9 "2026-04-17T05:56:53Z")

</div>

Cache eviction algorithms (LRU, SIEVE, FIFO) don't know the keys ahead of time. They need to find the 'nth oldest' entry based on insertion order, then evict it by key. That's exactly what the previous delegate showed with SIEVE. (The related thread claim)

Current workaround: `const [key, value] = [...map.entries()][index]` — which allocates a full array of all entries just to get one. The algorithm then discards that array. For large Maps (millions of entries), this is measurable memory churn and GC pressure.

`.entryAt(n)` would avoid that allocation. Same O(n) iteration under the hood (if engines choose), but no temporary array. The engine can walk the internal linked list until it reaches the nth node and return just that entry.

So the use case is: algorithms that need to examine entries by insertion order without knowing their keys. These exist today in caching libraries — and they all currently pay the spread-to-array tax.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [April 17, 2026, 6:32am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/10 "2026-04-17T06:32:39Z")

</div>

Wouldn't a parallel array of keys give you this? A Map subclass could abstract over it.

---

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 17, 2026, 7:06am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/11 "2026-04-17T07:06:37Z")

</div>

A parallel array works, but it doubles memory usage (Map + array). For large caches with millions of entries, that's a real cost. It also requires manual synchronisation — every set/delete must update both structures, which adds maintenance overhead and bug surface.

The Map already tracks insertion order internally via a linked list. That memory is already allocated. `entryAt(n)` just exposes read-only access to that existing order — no extra memory, no sync bugs.

A subclass could work, but it can't avoid the memory overhead of the parallel array unless it accesses the internal linked list, which isn't exposed to user code. That's why this needs a spec change.

---

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 17, 2026, 10:39am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/12 "2026-04-17T10:39:40Z")

</div>

Yes, that works. But the proposal isn't about whether something works—it's about whether the ergonomic and memory-footprint gap is worth standardising. The ecosystem already has `[...map.values()][n]` . We didn't need `Array.prototype.at` either. We added it because negative indexing and cleaner syntax have value. Same principle here

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [April 17, 2026, 5:15pm UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/13 "2026-04-17T17:15:53Z")

</div>

"Via a linked list" would mean the cost of `.drop(n).take(1)` isn't avoidable, because it has to navigate the links :-)

---

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 18, 2026, 12:47am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/14 "2026-04-18T00:47:18Z")

</div>

You're right — both are O(n). The difference is memory, not time.  
`map.at(n)` avoids allocating a temporary array of all entries, which matters for large Maps where spreading or `toArray()` causes memory spikes and GC pressure.  
The iteration cost is identical; the allocation cost is not.

---

<div class="post-metadata">

**Author:** ![conartist6](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/conartist6/32/515_2.png) [@conartist6](https://es.discourse.group/u/conartist6)\
**Post date:** [April 19, 2026, 12:45pm UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/15 "2026-04-19T12:45:44Z")

</div>

I think Jordan was suggesting that you use iterator helpers to do `keys().drop(n).take(1)` which won't cause memory spikes or GC pressure as `keys()` is an iterator and neither `drop` nor `take` will need to store more than one item at a time in memory.

---

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 19, 2026, 12:57pm UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/16 "2026-04-19T12:57:30Z")

</div>

Iterator helpers (`drop` , `take` ) are a great addition, but they're still Stage 3 and not yet universally available. More importantly, they require chaining multiple methods and calling `.next().value` to extract the result — which is less discoverable and ergonomic than a simple `.at(n)` .

`Array.prototype.at` already set the precedent: a simple, direct method for indexed access. Maps and Sets deserve the same. Iterator helpers are powerful, but they're a general-purpose tool; `.at(n)` is a specific, common operation that should be directly available.

---

<div class="post-metadata">

**Author:** ![conartist6](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/conartist6/32/515_2.png) [@conartist6](https://es.discourse.group/u/conartist6)\
**Post date:** [April 19, 2026, 1:08pm UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/17 "2026-04-19T13:08:29Z")

</div>

```javascript
function at(idx, iter) {
  let i = -1;
  for (let value of iter) {
    if (++i === idx) return value;
  }
}

let m = new Map([['foo', 'Foo'], ['bar', 'Bar']]);

at(1, m.keys()) // 'bar'

```

Edit: sorry for all the typos and edits to fix them! I accidentally hit enter before I was even half done, than paniced trying to fix it quickly

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [April 20, 2026, 12:40am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/18 "2026-04-20T00:40:31Z")

</div>

Stage 3 is when they’re safe to use, and they’re pretty universally shipped afaik - and polyfillable on every engine.

The difference is that arrays are indexed collections - not just ordered ones. Maps and Sets aren’t indexed, only ordered. Ordering doesn’t imply that an index makes sense.

---

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 20, 2026, 4:15am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/19 "2026-04-20T04:15:18Z")

</div>

You're right — a utility function works. The same could be said for `Array.prototype.at` (`arr.at(n)` vs `function at(arr, n) { return arr[n]; }` ). But we added it anyway because:

1. **Discoverability** — New developers shouldn't need to find or write this utility
2. **Consistency** — Arrays have `.at()` , Maps and Sets should too
3. **Chainability** — `map.at(n)` works inline without nesting
4. **Performance** — Native implementation can potentially optimize beyond userland loops
5. **Precedent** — We add convenience methods all the time (e.g., `Array.prototype.find` vs manual loop)

\*The question isn't 'can this be done?' — it's 'should this be a built-in for consistency and ergonomics?' I believe yes.

---

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 20, 2026, 4:17am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/20 "2026-04-20T04:17:09Z")

</div>

You're right — Maps and Sets aren't fundamentally indexed collections. But they _ **are** _ ordered, and developers frequently need to access entries by position (cache eviction, debugging, pagination).

The question isn't 'should Maps become Arrays?' — it's 'should ordered collections support indexed read-only access for convenience?'

We already have `[...map.keys()][n]` — a clunky, allocation-heavy version of this. `.at(n)` would be the same operation, but cleaner and without the memory spike.

This isn't changing the nature of Maps. It's just providing a direct path for an operation developers already perform.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [April 20, 2026, 4:32am UTC](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541/21 "2026-04-20T04:32:02Z")

</div>

> developers frequently need to access entries by position (cache eviction, debugging, pagination).

This has not been my experience. For debugging, I have only ever seen the entire contents dumped out; for pagination, I've only ever seen an Array (what would be the point of using a set or map for that?). Can you explain a bit about the cache eviction use case?

[Next page](https://es.discourse.group/t/map-and-set-getters-to-give-a-way-to-get-an-item-at-an-order/2541.md?page=2)
