biome complexity/useFlatMap
labelled safe
Analysis Nemotron 3 Super
The rule flags uses of .map() followed by .flat() where flat() has no argument or an explicit depth of 1, suggesting replacement with .flatMap() to avoid creating an intermediate array.
Fix: Replace the chained .map().flat() or .map().flat(1) call with a single .flatMap() call using the same mapping function, preserving all arguments and surrounding syntax.
8 hazards
- If the map callback contains side effects that depend on intermediate array indices, removing the intermediate array may alter behavior if those indices are used elsewhere.
- If the flat depth is omitted but the map returns nested arrays deeper than one level, flatMap only flattens one level, potentially leaving extra nesting compared to the original.
- If the map function returns holes (empty slots), .flat() removes them while .flatMap() preserves them, changing array length and indices.
- If the map function uses 'this' and the context is bound via .map(callback, thisArg), the fix must preserve the thisArg or it will be lost.
- If the chain includes .map().flat() after a non-array method like .filter(), the fix only applies to the map-flat pair and must not disturb surrounding chain links.
- If the map callback throws an exception, the timing of when that exception occurs relative to subsequent operations is unchanged, but any reliance on the intermediate array's existence for debugging or inspection is removed.
- If the code relies on the intermediate array being a distinct object in memory (e.g., for identity checks via ===), replacing it with flatMap's internal optimization breaks such checks.
- If the map function returns undefined, .flat() skips it while .flatMap() also skips it, so no change, but if it returns null, both treat it as a single element, so still safe—however, if the map returns a non-array scalar, both behave identically in flattening one level.
Findings
| program | oracle | label | judge | status | summary |
|---|---|---|---|---|---|
| r1-p006 | fix-changes-behavior | labelled safe | program-bug · low | related | The program fails both before and after the fix because `arguments` is an array-like object without `map` or `flatMap` methods. The rewrite does not change the… |
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Var Hoisting and Re-declaration | var hoisting and re-declaration inside loops or after use |
| r1-p002 | silent prediction off | Holes Preservation Difference | If the map function returns holes (empty slots), .flat() removes them while .flatMap() preserves them, changing array length and indices. |
| r1-p003 | fix ok | Chain with Filter Before Map-Flat | If the chain includes .map().flat() after a non-array method like .filter(), the fix only applies to the map-flat pair and must not disturb surrounding chain links. |
| r1-p004 | silent prediction off | Intermediate Array Identity Check | If the code relies on the intermediate array being a distinct object in memory (e.g., for identity checks via ===), replacing it with flatMap's internal optimization breaks such checks. |
| r1-p005 | fix ok | Getter/Setter Side Effects | getters, setters and Object.defineProperty side effects |
| r1-p006 | fix-changes-behavior | Arguments Object and Parameters | arguments object, function.length, default parameters, rest parameters |
| r1-p007 | fix ok | Class Fields and Super Calls | class fields, static blocks, private names, super calls |
| r1-p008 | fix ok | Strict vs Sloppy Mode Semantics | strict vs sloppy mode semantics |
| r2-p001 | fix ok | Hole preservation difference | If the map function returns holes (empty slots), .flat() removes them while .flatMap() preserves them, changing array length and indices. |
| r2-p002 | fix ok | Class fields and super calls | class fields, static blocks, private names, super calls |
| r2-p003 | silent prediction off | ThisArg binding in map | If the map function uses 'this' and the context is bound via .map(callback, thisArg), the fix must preserve the thisArg or it will be lost. |
| r2-p004 | fix ok | Numeric/string coercion edge cases | numeric/string coercion: + vs template literal, objects with toString/valueOf, Symbol, BigInt |
| r2-p005 | silent | Async/await ordering and generator interaction | async/await ordering, generators and yield position |
| r2-p006 | fix ok | Arguments object and parameter nuances | arguments object, function.length, default parameters, rest parameters |
| r2-p007 | fix ok | Block-scoped function declarations | function declarations inside blocks, switch cases, labeled statements |
Sources
r1-p001 · Var Hoisting and Re-declaration
1for (var i = 0; i < 2; i++) {
2 var data = [[i], [i+1]];
3 var result = data.map(x => x).flat();
4 console.log(result.length);
5}
6console.log(typeof data);
--- orig.js 2026-10-17 16:12:12.090676088 -0400 +++ fixed.js 2026-10-17 16:12:12.359669828 -0400 @@ -1,6 +1,6 @@ for (var i = 0; i < 2; i++) { var data = [[i], [i+1]]; - var result = data.map(x => x).flat(); + var result = data.flatMap(x => x); console.log(result.length); } console.log(typeof data);
2 2 object
r1-p002 · Holes Preservation Difference
1const arr = [1, , 3];
2const mapped = arr.map((x, i) => i === 1 ? undefined : [x]);
3const flattened = mapped.flat();
4console.log(flattened.length, flattened.join(','));
5try {
6 const flatMapped = arr.flatMap((x, i) => i === 1 ? undefined : [x]);
7 console.log(flatMapped.length, flatMapped.join(','));
8} catch (e) { console.log(e.name + ': ' + e.message); }
2 1,3 2 1,3
r1-p003 · Chain with Filter Before Map-Flat
1const nums = [1, 2, 3, 4];
2const result = nums.filter(n => n % 2 === 0).map(n => [n, n*10]).flat();
3console.log(result.join('-'));
4// After fix: .flatMap should preserve filter behavior
--- orig.js 2026-10-17 16:12:12.755660612 -0400 +++ fixed.js 2026-10-17 16:12:13.014654585 -0400 @@ -1,4 +1,4 @@ const nums = [1, 2, 3, 4]; -const result = nums.filter(n => n % 2 === 0).map(n => [n, n*10]).flat(); +const result = nums.filter(n => n % 2 === 0).flatMap(n => [n, n*10]); console.log(result.join('-')); // After fix: .flatMap should preserve filter behavior
2-20-4-40
r1-p004 · Intermediate Array Identity Check
1const src = [[1], [2]];
2const mapped = src.map(x => x);
3const flattened = mapped.flat();
4console.log(mapped === src, mapped === flattened);
5// After fix, no intermediate array -> mapped would be undefined
false false
r1-p005 · Getter/Setter Side Effects
1let count = 0;
2const obj = { get arr() { count++; return [[1], [2]]; } };
3const result = obj.arr.map(x => x).flat();
4console.log(count, result.length);
5// After fix, getter called once instead of twice if map and flat were separate?
--- orig.js 2026-10-17 16:12:13.401645600 -0400 +++ fixed.js 2026-10-17 16:12:13.658639632 -0400 @@ -1,5 +1,5 @@ let count = 0; const obj = { get arr() { count++; return [[1], [2]]; } }; -const result = obj.arr.map(x => x).flat(); +const result = obj.arr.flatMap(x => x); console.log(count, result.length); // After fix, getter called once instead of twice if map and flat were separate?
1 2
r1-p006 · Arguments Object and Parameters
1function process(a, b = [], ...rest) {
2 return arguments.map(x => [x]).flat();
3}
4const res = process(1, 2, 3);
5console.log(res.length, res.join(','));
--- orig.js 2026-10-17 16:12:13.830635639 -0400 +++ fixed.js 2026-10-17 16:12:14.086629700 -0400 @@ -1,5 +1,5 @@ function process(a, b = [], ...rest) { - return arguments.map(x => [x]).flat(); + return arguments.flatMap(x => [x]); } const res = process(1, 2, 3); console.log(res.length, res.join(','));
r1-p007 · Class Fields and Super Calls
1class Base {
2 static items = [[1], [2]];
3 getData() { return Base.items.map(x => x).flat(); }
4}
5class Der extends Base {
6 getData() { return super.getData().length; }
7}
8console.log(new Der().getData());
--- orig.js 2026-10-17 16:12:14.245626016 -0400 +++ fixed.js 2026-10-17 16:12:14.506619970 -0400 @@ -1,6 +1,6 @@ class Base { static items = [[1], [2]]; - getData() { return Base.items.map(x => x).flat(); } + getData() { return Base.items.flatMap(x => x); } } class Der extends Base { getData() { return super.getData().length; }
2
r1-p008 · Strict vs Sloppy Mode Semantics
1'use strict';
2(function() {
3 var data = [[1], [2], [3]];
4 try {
5 delete data[0];
6 const result = data.map(x => x).flat();
7 console.log(result.length);
8 } catch (e) { console.log(e.name); }
9})();
10
11(function() {
12 'use sloppy';
13 var data2 = [[1], [2], [3]];
14 delete data2[0];
15 const result = data2.map(x => x).flat();
16 console.log(result.length);
17})();
--- orig.js 2026-10-17 16:12:14.671616148 -0400 +++ fixed.js 2026-10-17 16:12:14.931610125 -0400 @@ -3,7 +3,7 @@ var data = [[1], [2], [3]]; try { delete data[0]; - const result = data.map(x => x).flat(); + const result = data.flatMap(x => x); console.log(result.length); } catch (e) { console.log(e.name); } })(); @@ -12,6 +12,6 @@ 'use sloppy'; var data2 = [[1], [2], [3]]; delete data2[0]; - const result = data2.map(x => x).flat(); + const result = data2.flatMap(x => x); console.log(result.length); })();
2 2
r2-p001 · Hole preservation difference
1const a = [1, , 3];
2const mapped = a.map(x => x === 2 ? [] : [x]);
3console.log('mapped:', mapped);
4const flatResult = mapped.flat();
5console.log('flat():', flatResult, 'length:', flatResult.length);
6a.map(x => x === 2 ? [] : [x]).flat(); // expect_trigger
7const flatMapResult = a.map(x => x === 2 ? [] : [x]).flatMap(x => x);
8console.log('flatMap():', flatMapResult, 'length:', flatMapResult.length);
--- orig.js 2026-10-17 16:12:25.971357806 -0400 +++ fixed.js 2026-10-17 16:12:26.250351516 -0400 @@ -3,6 +3,6 @@ console.log('mapped:', mapped); const flatResult = mapped.flat(); console.log('flat():', flatResult, 'length:', flatResult.length); -a.map(x => x === 2 ? [] : [x]).flat(); // expect_trigger +a.flatMap(x => x === 2 ? [] : [x]); // expect_trigger const flatMapResult = a.map(x => x === 2 ? [] : [x]).flatMap(x => x); console.log('flatMap():', flatMapResult, 'length:', flatMapResult.length);
mapped: [ [ 1 ], <1 empty item>, [ 3 ] ] flat(): [ 1, 3 ] length: 2 flatMap(): [ 1, 3 ] length: 2
r2-p002 · Class fields and super calls
1class Base { constructor() { this.x = 1; } static y = 2; }
2class Derived extends Base { #z = 3; static { console.log('static block'); }
3 getZ() { return this.#z + super.x; }
4 process() { return [this].map(i => [i.getZ()]).flat(); }
5}
6const d = new Derived();
7console.log('process result:', d.process());
8d.process(); // expect_trigger
--- orig.js 2026-10-17 16:12:26.411347887 -0400 +++ fixed.js 2026-10-17 16:12:26.672342004 -0400 @@ -1,7 +1,7 @@ class Base { constructor() { this.x = 1; } static y = 2; } class Derived extends Base { #z = 3; static { console.log('static block'); } getZ() { return this.#z + super.x; } - process() { return [this].map(i => [i.getZ()]).flat(); } + process() { return [this].flatMap(i => [i.getZ()]); } } const d = new Derived(); console.log('process result:', d.process());
static block process result: [ NaN ]
r2-p003 · ThisArg binding in map
1const obj = { multiplier: 2 };
2function mapper(x) { return [x * this.multiplier]; }
3const arr = [1, 2, 3];
4const resultWithThis = arr.map(mapper, obj).flat();
5console.log('with thisArg:', resultWithThis);
6arr.map(mapper, obj).flat(); // expect_trigger
7const broken = arr.map(mapper).flatMap(x => x);
8console.log('without thisArg:', broken);
with thisArg: [ 2, 4, 6 ]
r2-p004 · Numeric/string coercion edge cases
1const weird = {
2 valueOf() { return 42; },
3 toString() { return 'hello'; }
4};
5const sym = Symbol('test');
6const big = BigInt(123);
7const arr = [weird, sym, big];
8const mapped = arr.map(x => [x, String(x)]);
9console.log('mapped:', mapped);
10const flattened = mapped.flat();
11console.log('flat result:', flattened);
12arr.map(x => [x, String(x)]).flat(); // expect_trigger
13const flatMapped = arr.map(x => [x, String(x)]).flatMap(x => x);
14console.log('flatMap result:', flatMapped);
--- orig.js 2026-10-17 16:12:27.071333013 -0400 +++ fixed.js 2026-10-17 16:12:27.346326828 -0400 @@ -9,6 +9,6 @@ console.log('mapped:', mapped); const flattened = mapped.flat(); console.log('flat result:', flattened); -arr.map(x => [x, String(x)]).flat(); // expect_trigger +arr.flatMap(x => [x, String(x)]); // expect_trigger const flatMapped = arr.map(x => [x, String(x)]).flatMap(x => x); console.log('flatMap result:', flatMapped);
mapped: [
[
{ valueOf: [Function: valueOf], toString: [Function: toString] },
'hello'
],
[ Symbol(test), 'Symbol(test)' ],
[ 123n, '123' ]
]
flat result: [
{ valueOf: [Function: valueOf], toString: [Function: toString] },
'hello',
Symbol(test),
'Symbol(test)',
123n,
'123'
]
flatMap result: [
{ valueOf: [Function: valueOf], toString: [Function: toString] },
'hello',
Symbol(test),
'Symbol(test)',
123n,
'123'
]r2-p005 · Async/await ordering and generator interaction
1async function* gen() { yield 1; yield* [2, 3]; }
2(async () => {
3 const arr = [];
4 for await (const x of gen()) arr.push(x);
5 console.log('collected:', arr);
6 const mapped = arr.map(x => [x, x * 2]);
7 console.log('mapped:', mapped);
8 const result = mapped.flat();
9 console.log('flat:', result);
10})();
collected: [ 1, 2, 3 ] mapped: [ [ 1, 2 ], [ 2, 4 ], [ 3, 6 ] ] flat: [ 1, 2, 2, 4, 3, 6 ]
r2-p006 · Arguments object and parameter nuances
1function mapper(a, b = 10, ...rest) {
2 console.log('args.length:', arguments.length, 'b:', b, 'rest:', rest);
3 return [a + b];
4}
5const arr = [5];
6console.log('calling map:');
7const result = arr.map(mapper).flat();
8console.log('flat result:', result);
9arr.map(mapper).flat(); // expect_trigger
10const flatMapResult = arr.map(mapper).flatMap(x => x);
11console.log('flatMap result:', flatMapResult);
--- orig.js 2026-10-17 16:12:27.761317495 -0400 +++ fixed.js 2026-10-17 16:12:28.039311245 -0400 @@ -4,8 +4,8 @@ } const arr = [5]; console.log('calling map:'); -const result = arr.map(mapper).flat(); +const result = arr.flatMap(mapper); console.log('flat result:', result); -arr.map(mapper).flat(); // expect_trigger +arr.flatMap(mapper); // expect_trigger const flatMapResult = arr.map(mapper).flatMap(x => x); console.log('flatMap result:', flatMapResult);
calling map: args.length: 3 b: 0 rest: [ [ 5 ] ] flat result: [ 5 ] args.length: 3 b: 0 rest: [ [ 5 ] ] args.length: 3 b: 0 rest: [ [ 5 ] ] flatMap result: [ 5 ]
r2-p007 · Block-scoped function declarations
1function test() {
2 if (true) {
3 function mapFn(x) { return [x, 0]; }
4 const arr = [1, 2];
5 console.log('mapped:', arr.map(mapFn));
6 const flat = arr.map(mapFn).flat();
7 console.log('flat:', flat);
8 arr.map(mapFn).flat(); // expect_trigger
9 }
10 switch (1) {
11 case 1:
12 function caseFn(x) { return [x * 2]; }
13 break;
14 }
15}
16test();
--- orig.js 2026-10-17 16:12:28.211307386 -0400 +++ fixed.js 2026-10-17 16:12:28.487301193 -0400 @@ -3,9 +3,9 @@ function mapFn(x) { return [x, 0]; } const arr = [1, 2]; console.log('mapped:', arr.map(mapFn)); - const flat = arr.map(mapFn).flat(); + const flat = arr.flatMap(mapFn); console.log('flat:', flat); - arr.map(mapFn).flat(); // expect_trigger + arr.flatMap(mapFn); // expect_trigger } switch (1) { case 1:
mapped: [ [ 1, 0 ], [ 2, 0 ] ] flat: [ 1, 0, 2, 0 ]