JavaScript Basics

JavaScript Array flat() Method: Flatten Nested Arrays

Master the JavaScript Array flat() method to flatten nested arrays. Learn syntax, depth control, practical examples, and best practices for array flattening.

By JavaScript Document Team
arraysmethodsbasicses2019flattening

The flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth. Introduced in ES2019, it provides a clean and efficient way to flatten nested arrays.

Understanding Array flat()

The flat() method is designed to handle nested arrays by "flattening" them - combining elements from nested arrays into a single-level array. It's particularly useful when dealing with data structures that contain arrays within arrays.

Syntax

array.flat([depth]);

Parameters

  • depth (optional): The depth level specifying how deep a nested array structure should be flattened
    • Default value is 1
    • Use Infinity to flatten all levels
    • Must be a positive number or Infinity

Return Value

A new array with the sub-array elements concatenated into it.

Basic Usage

Single-Level Flattening

// Default behavior (depth = 1)
const arr1 = [1, 2, [3, 4]];
console.log(arr1.flat()); // [1, 2, 3, 4]

const arr2 = ['a', ['b', 'c'], 'd'];
console.log(arr2.flat()); // ['a', 'b', 'c', 'd']

// Mixed types
const mixed = [1, [true, 'hello'], null, [undefined, []]];
console.log(mixed.flat()); // [1, true, 'hello', null, undefined, []]

Multi-Level Flattening

const nested = [1, [2, [3, [4, [5]]]]];

// Different depth levels
console.log(nested.flat()); // [1, 2, [3, [4, [5]]]]
console.log(nested.flat(2)); // [1, 2, 3, [4, [5]]]
console.log(nested.flat(3)); // [1, 2, 3, 4, [5]]
console.log(nested.flat(4)); // [1, 2, 3, 4, 5]

// Using Infinity for complete flattening
console.log(nested.flat(Infinity)); // [1, 2, 3, 4, 5]

Practical Examples

Data Processing

// Processing nested data structures
const salesData = [
  ['Q1', [100, 200, 150]],
  ['Q2', [300, 250, 400]],
  ['Q3', [200, 300]],
  ['Q4', [500, 450, 600, 700]],
];

// Extract all sales figures
const allSales = salesData.map((quarter) => quarter[1]).flat();

console.log(allSales);
// [100, 200, 150, 300, 250, 400, 200, 300, 500, 450, 600, 700]

// Calculate total
const totalSales = allSales.reduce((sum, sale) => sum + sale, 0);
console.log(`Total sales: $${totalSales}`); // Total sales: $3850

Menu Structure Flattening

const menuStructure = [
  {
    name: 'File',
    items: [
      'New',
      'Open',
      {
        name: 'Recent',
        items: ['Document1', 'Document2', 'Document3'],
      },
      'Save',
      'Exit',
    ],
  },
  {
    name: 'Edit',
    items: ['Cut', 'Copy', 'Paste'],
  },
  {
    name: 'View',
    items: [
      'Zoom',
      {
        name: 'Panels',
        items: ['Navigator', 'History', 'Layers'],
      },
    ],
  },
];

// Extract all menu items
function extractMenuItems(menu) {
  return menu
    .map((section) => {
      return section.items.map((item) =>
        typeof item === 'string' ? item : [item.name, ...item.items]
      );
    })
    .flat(2);
}

console.log(extractMenuItems(menuStructure));
// ['New', 'Open', 'Recent', 'Document1', 'Document2', 'Document3', 'Save', 'Exit', 'Cut', 'Copy', 'Paste', 'Zoom', 'Panels', 'Navigator', 'History', 'Layers']

Removing Empty Slots

// flat() removes empty slots in arrays
const sparseArray = [1, , 3, , , 6];
console.log(sparseArray); // [1, empty, 3, empty × 2, 6]
console.log(sparseArray.flat()); // [1, 3, 6]

// Nested sparse arrays
const nestedSparse = [1, [2, , 4], , [5, , , 8], 9];
console.log(nestedSparse.flat()); // [1, 2, 4, 5, 8, 9]

Working with Complex Data

Tree Structure Flattening

class TreeFlattener {
  constructor() {
    this.nodes = [];
  }

  // Convert tree to nested array structure
  treeToNestedArray(node) {
    if (!node) return [];

    const result = [node.value];
    if (node.children && node.children.length > 0) {
      result.push(node.children.map((child) => this.treeToNestedArray(child)));
    }

    return result;
  }

  // Flatten tree to get all values
  flattenTree(root, depth = Infinity) {
    const nestedArray = this.treeToNestedArray(root);
    return nestedArray.flat(depth);
  }
}

// Example tree
const tree = {
  value: 1,
  children: [
    {
      value: 2,
      children: [
        { value: 4, children: [] },
        { value: 5, children: [] },
      ],
    },
    {
      value: 3,
      children: [
        {
          value: 6,
          children: [{ value: 7, children: [] }],
        },
      ],
    },
  ],
};

const flattener = new TreeFlattener();
console.log(flattener.flattenTree(tree));
// [1, 2, 4, 5, 3, 6, 7]

Matrix Operations

// 2D Matrix flattening
class Matrix {
  constructor(data) {
    this.data = data;
  }

  // Flatten matrix to 1D array
  flatten() {
    return this.data.flat();
  }

  // Get all values in row-major order
  toArray() {
    return this.flatten();
  }

  // Get specific row
  getRow(index) {
    return this.data[index];
  }

  // Get specific column
  getColumn(index) {
    return this.data.map((row) => row[index]);
  }

  // Get all unique values
  uniqueValues() {
    return [...new Set(this.flatten())];
  }
}

const matrix = new Matrix([
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
]);

console.log(matrix.flatten()); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(matrix.uniqueValues()); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

Combining with Other Methods

flat() with map()

// Traditional approach with map and flat
const sentences = ['Hello world', 'How are you', 'JavaScript is awesome'];

// Split into words - creates nested arrays
const nestedWords = sentences.map((sentence) => sentence.split(' '));
console.log(nestedWords);
// [['Hello', 'world'], ['How', 'are', 'you'], ['JavaScript', 'is', 'awesome']]

// Flatten to get all words
const allWords = nestedWords.flat();
console.log(allWords);
// ['Hello', 'world', 'How', 'are', 'you', 'JavaScript', 'is', 'awesome']

// Or combine in one step (though flatMap is more efficient)
const words = sentences.map((s) => s.split(' ')).flat();

flat() with filter()

// Remove falsy values from nested arrays
const data = [
  [1, 0, 2],
  [false, 3, null],
  [undefined, 4, ''],
  [5, NaN, 6],
];

const truthyValues = data.flat().filter(Boolean);

console.log(truthyValues); // [1, 2, 3, 4, 5, 6]

// Extract and filter specific nested data
const users = [
  { name: 'Alice', skills: ['JS', 'React', 'Node'] },
  { name: 'Bob', skills: ['Python', 'JS'] },
  { name: 'Charlie', skills: ['Java', 'Spring', 'JS'] },
];

const allJSUsers = users
  .map((user) => (user.skills.includes('JS') ? user.name : []))
  .flat();

console.log(allJSUsers); // ['Alice', 'Bob', 'Charlie']

Performance Considerations

Depth Impact

// Performance comparison for different depths
const createDeeplyNested = (depth, size = 1000) => {
  let arr = Array(size).fill(1);
  for (let i = 0; i < depth; i++) {
    arr = [arr];
  }
  return arr;
};

// Shallow nesting - fast
console.time('Shallow flat');
const shallow = createDeeplyNested(2, 10000);
shallow.flat(2);
console.timeEnd('Shallow flat');

// Deep nesting - slower
console.time('Deep flat');
const deep = createDeeplyNested(10, 1000);
deep.flat(10);
console.timeEnd('Deep flat');

// Infinity - use with caution
console.time('Infinity flat');
const veryDeep = createDeeplyNested(5, 1000);
veryDeep.flat(Infinity);
console.timeEnd('Infinity flat');

Alternative Approaches

// Manual recursive flattening (pre-ES2019)
function flattenRecursive(arr, depth = 1) {
  if (depth < 1) return arr.slice();

  return arr.reduce((acc, val) => {
    if (Array.isArray(val) && depth > 0) {
      return acc.concat(flattenRecursive(val, depth - 1));
    }
    return acc.concat(val);
  }, []);
}

// Using spread operator (shallow only)
function flattenShallow(arr) {
  return [].concat(...arr);
}

// Performance test
const testArray = [
  [1, 2],
  [3, 4],
  [5, 6],
];

console.time('flat()');
testArray.flat();
console.timeEnd('flat()');

console.time('spread');
flattenShallow(testArray);
console.timeEnd('spread');

console.time('recursive');
flattenRecursive(testArray);
console.timeEnd('recursive');

Edge Cases and Gotchas

Non-Array Elements

// flat() only flattens arrays, not other iterables
const mixed = [
  [1, 2],
  new Set([3, 4]), // Set is not flattened
  '56', // String is not split
  { 0: 7, 1: 8, length: 2 }, // Array-like object is not flattened
  [9, 10],
];

console.log(mixed.flat());
// [1, 2, Set(2), '56', {0: 7, 1: 8, length: 2}, 9, 10]

Circular References

// Be careful with circular references
const arr = [1, 2, 3];
arr.push([4, 5, arr]); // Circular reference

try {
  // This will work for depth 1
  console.log(arr.flat()); // [1, 2, 3, 4, 5, [1, 2, 3, [...]]]

  // But Infinity depth may cause issues
  // console.log(arr.flat(Infinity)); // May cause stack overflow
} catch (e) {
  console.error('Error:', e.message);
}

Preserving Array Properties

// flat() doesn't preserve array properties
const arr = [1, [2, 3], 4];
arr.customProp = 'test';
arr[1].nestedProp = 'nested';

const flattened = arr.flat();
console.log(flattened); // [1, 2, 3, 4]
console.log(flattened.customProp); // undefined

Practical Utilities

Deep Flatten with Type Checking

function safeFlatten(arr, depth = 1) {
  if (!Array.isArray(arr)) {
    throw new TypeError('First argument must be an array');
  }

  if (depth !== Infinity && (!Number.isInteger(depth) || depth < 0)) {
    throw new RangeError('Depth must be a non-negative integer or Infinity');
  }

  try {
    return arr.flat(depth);
  } catch (e) {
    console.error('Flattening failed:', e);
    return arr;
  }
}

// Usage
console.log(
  safeFlatten(
    [
      [1, 2],
      [3, 4],
    ],
    1
  )
); // [1, 2, 3, 4]
console.log(safeFlatten([[1, [2, [3]]]], Infinity)); // [1, 2, 3]

Flatten with Transformation

function flatMap(arr, callback, depth = 1) {
  return arr.map(callback).flat(depth);
}

// Example: Extract and flatten nested properties
const data = [
  { values: [1, 2, 3] },
  { values: [4, 5] },
  { values: [6, 7, 8, 9] },
];

const result = flatMap(data, (item) => item.values);
console.log(result); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

Browser Compatibility

The flat() method is supported in modern browsers but may need a polyfill for older environments:

// Simple polyfill for Array.prototype.flat
if (!Array.prototype.flat) {
  Array.prototype.flat = function (depth = 1) {
    return depth > 0
      ? this.reduce(
          (acc, val) =>
            acc.concat(Array.isArray(val) ? val.flat(depth - 1) : val),
          []
        )
      : this.slice();
  };
}

Best Practices

  1. Use Appropriate Depth: Don't use Infinity unless necessary
  2. Consider flatMap(): For map + flat(1) operations
  3. Handle Edge Cases: Check for non-array elements and circular references
  4. Performance: Be mindful of deeply nested structures
  5. Type Safety: Validate inputs when building utilities

Conclusion

The flat() method is a powerful tool for working with nested arrays, providing a clean and efficient way to flatten array structures to any desired depth. Whether you're processing data, working with tree structures, or simply need to combine nested arrays, flat() offers a straightforward solution. Understanding its behavior with different depths and edge cases will help you use it effectively in your JavaScript applications.