As pointed out in the comments, since each element is indeed touched only once, the time complexity is intuitively O(N).

However, because each recursive call to flatten creates a new intermediate array, the run-time depends strongly on the structure of the input array.


A non-trivial1 example of such a case would be when the array is organized similarly to a full binary tree:

[[[a, b], [c, d]], [[e, f], [g, h]]], [[[i, j], [k, l]], [[m, n], [o, p]]]

               |
        ______ + ______
       |               |
    __ + __         __ + __
   |       |       |       |
 _ + _   _ + _   _ + _   _ + _
| | | | | | | | | | | | | | | | 
a b c d e f g h i j k l m n o p

The time complexity recurrence relation is:

T(n) = 2 * T(n / 2) + O(n)

Where 2 * T(n / 2) comes from recursive calls to flatten the sub-trees, and O(n) from pushing2 the results, which are two arrays of length n / 2.

The Master theorem states that in this case T(N) = O(N log N), not O(N) as expected.

1) non-trivial means that no element is wrapped unnecessarily, e.g. [[[a]]].

2) This implicitly assumes that k push operations are O(k) amortized, which is not guaranteed by the standard, but is still true for most implementations.


A "true" O(N) solution will directly append to the final output array instead of creating intermediate arrays:

function flatten_linear(items) {
  const flat = [];
  
  // do not call the whole function recursively
  // ... that's this mule function's job
  function inner(input) {
     if (Array.isArray(input))
        input.forEach(inner);
     else
        flat.push(input);
  }
  
  // call on the "root" array
  inner(items);  

  return flat;
}

The recurrence becomes T(n) = 2 * T(n / 2) + O(1) for the previous example, which is linear.

Again this assumes both 1) and 2).

Answer from meowgoesthedog on Stack Overflow
Top answer
1 of 1
11

As pointed out in the comments, since each element is indeed touched only once, the time complexity is intuitively O(N).

However, because each recursive call to flatten creates a new intermediate array, the run-time depends strongly on the structure of the input array.


A non-trivial1 example of such a case would be when the array is organized similarly to a full binary tree:

[[[a, b], [c, d]], [[e, f], [g, h]]], [[[i, j], [k, l]], [[m, n], [o, p]]]

               |
        ______ + ______
       |               |
    __ + __         __ + __
   |       |       |       |
 _ + _   _ + _   _ + _   _ + _
| | | | | | | | | | | | | | | | 
a b c d e f g h i j k l m n o p

The time complexity recurrence relation is:

T(n) = 2 * T(n / 2) + O(n)

Where 2 * T(n / 2) comes from recursive calls to flatten the sub-trees, and O(n) from pushing2 the results, which are two arrays of length n / 2.

The Master theorem states that in this case T(N) = O(N log N), not O(N) as expected.

1) non-trivial means that no element is wrapped unnecessarily, e.g. [[[a]]].

2) This implicitly assumes that k push operations are O(k) amortized, which is not guaranteed by the standard, but is still true for most implementations.


A "true" O(N) solution will directly append to the final output array instead of creating intermediate arrays:

function flatten_linear(items) {
  const flat = [];
  
  // do not call the whole function recursively
  // ... that's this mule function's job
  function inner(input) {
     if (Array.isArray(input))
        input.forEach(inner);
     else
        flat.push(input);
  }
  
  // call on the "root" array
  inner(items);  

  return flat;
}

The recurrence becomes T(n) = 2 * T(n / 2) + O(1) for the previous example, which is linear.

Again this assumes both 1) and 2).

🌐
GitHub
gist.github.com › nitely › 21735cf83c8867b9004e203e78aae76d
Flatten array in JavaScript · GitHub
Elements is the sum of integers and nested arrays. There may be a better way to express this in Big O notation, but idk how. [0]: that number is 3 between push, copy, and pop; it's not done for every element, is only done once. Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment · You can’t perform that action at this time.
Discussions

Javascript Interview Question: Flatten an Array (in-depth)

if I were an interviewer, I think I would be satisfied by your answer, but as a fellow programmer I think that despite the current situation with tail call optimisation, at least attempting a tail recursive solution is worth it, because you can 1) be future proof for when the tail call situation improves and 2) use a trampoline to put a tail-call style recursive function into an iterative process in the meantime, which solves the issue.

Here is an example of a tail-recursive flatten function that uses a "trampoline" to put each recursive call into an iterative while loop. It's probably not the most efficient way of going about things, but due to the trampoline, there should be no stack growth issue. I also opted to not "return a continuation" for the continuation part of the trampoline. Returning a continuation function would increase the amount of inner function executions and definitions, so instead I just take a "snapshot" of the arguments and call the original function with those arguments.

const trampoline = fn => (...args) => {
  let step = fn(...args);
  while (!step.done) {
    step = fn(...step.args);
  }
  return step.result;
}

trampoline.done = result => ({ done: true, result });
trampoline.next = (...args) => ({ done: false, args });

const flatten = trampoline((list, accumulator = []) => {
  if (list.length === 0) {
    return trampoline.done(accumulator);
  }
  const [head, ...tail] = list;
  if (Array.isArray(head)) {
    return trampoline.next([...head, ...tail], accumulator);
  }
  return trampoline.next(tail, [...accumulator, head]);
});

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

More on reddit.com
🌐 r/javascript
13
6
October 25, 2018
python - How can I calculate the run-time complexity of this flatten array function if number of items is unknown? - Stack Overflow
Exact size isn't really too important when it comes to complexity. Constants get dropped anyway. If you have a 3d array, internally it has to loop through all the items, even if that's not obvious by the code. So the answer is O(n**3) because it needs a loop inside a loop inside a loop. More on stackoverflow.com
🌐 stackoverflow.com
How to flat an array of arrays and order it in javascript - Stack Overflow
Is there a better way to flat an array of arrays of integers? this solution is easy but I really don't know if it's time complexity is the best. const list = (arr) => { //to avoid mutating the the More on stackoverflow.com
🌐 stackoverflow.com
What is the time complexity of this code?
I think the interviewer is somewhat confused about how time-complexity works (or maybe something got lost in communication). When you mention a time complexity like O(NxM), you need to clarify what N and M mean. Given the context here, one can assume N = size of the outer slice and M is size of each nested slice. Now, if you wanted to flatten the 2D array into a 1D array, you will have to visit every single element in the 2D array. It doesn't matter how you implement it, since you need to visit every element of the 2D array at least once, the lower bound on your implementation is going to be O(NxM). No way around it, irrespective of the programming language or how you implement it syntactically within that language. The person wanted me to do it in 1 loop, but he also wanted not to have any O(n*m) complexity. It is possible to implement it in 1 loop, but that does not mean it is not still O(NxM) complexity. Something like this: https://go.dev/play/p/FQ7ZpJ16xwj More on reddit.com
🌐 r/golang
31
28
June 28, 2024
Top answer
1 of 2
3

No, the code you've shown has neither exponential nor linear time complexity.

Before we can determine the complexity of any algorithm, we need to decide how to measure the size of the input. For the particular case of flatting an array, many options exist. We can count the number of arrays in the input, the number of array elements (sum of all array lengths), the average array length, the number of non-array elements in all arrays, the likelyhood that an array element is an array, the average number of elements that are arrays, etc.

I think what makes the most sense to gauge algorithms for this problem are the number of array elements in the whole input - let's call it e - and the average depth of these elements - let's call it d.

Now there are two standard approaches to this problem. The algorithm you've shown

const flattenDeep = (array) => {
  const flat = [];
  for (let element of array) {
    Array.isArray(element)
      ? flat.push(...flattenDeep(element))
      : flat.push(element);
  }
  return flat;
}

does have a time complexity of O(e * d). It is the naive approach, also demonstrated in the shorter code

const flattenDeep = x => Array.isArray(x) ? x.flatMap(flattenDeep) : [x];

or in the slightly longer loop

const flattenDeep = (array) => {
  const flat = [];
  for (const element of array) {
    if (Array.isArray(element)) {
      flat.push(...flattenDeep(element))
    } else {
      flat.push(element);
    }
  }
  return flat;
}

Both of them have the problem that they are more-or-less-explicit nested loops, where the inner one loops over the result of the recursive call. Notice that the spread syntax in the call flat.push(...flattenDeep(element)) amounts to basically

for (const val of flattenDeep(element)) flat.push(val);

This is pretty bad, consider what happens for the worst case input [[[…[[[1,2,3,…,n]]]…]]].

The second standard approach is to directly put non-array elements into the final result array - without creating, returning and iterating any temporary arrays:

function flattenDeep(array) {
  const flat = [];
  function recurse(val) {
    if (Array.isArray(val)) {
      for (const el of val) {
        recurse(el);
      }
    } else {
      flat.push(val);
    }
  }
  recurse(array);
  return flat;
}

This is a much better solution, it has a linear time complexity of O(e) - d doesn't factor in at all any more.

2 of 2
-2
  • the exploration of a regular structure can be done, however, for models that use addressing it is necessary to control the exploration, otherwise it may have infinite recursion.. i.e:

    var a = [ 1, 2, 3 ]; var t = [ ]; t.push(a); t.push(t);

    console.log(t);

infinit loop example

  • action: check if your array value already explored.
🌐
Educative
educative.io › home › courses › master the javascript interview › flatten array
Flatten Array - Master the JavaScript Interview
It should return a new array that contains the items of each internal array, preserving order. ... flatten([ [ [ [1], 2], 3], [4], [], [[5]]]); // -> [1, 2, 3, 4, 5] flatten(['abc', ['def', ['ghi', ['jkl']]]]); // -> ['abc', 'def', 'ghi', 'jkl'] As in the last problem, we have to process every item we receive. There’s no way to get around that so the best time complexity we ...
🌐
Javascript360
javascript360.org › day 26: flatten deeply nested array
Day 26: Flatten Deeply Nested Array | JavaScript360.org
Therefore, the time complexity of the code is O(m), where m is the total number of elements in the input array. ... The space complexity of the code is O(m), where m is the total number of elements in the input array.
🌐
DEV Community
dev.to › lukocastillo › time-complexity-big-0-for-javascript-array-methods-and-examples-mlg
Time complexity Big 0 for Javascript Array methods and examples. - DEV Community
June 3, 2020 - So, let's start with a quick definition of the method, his time complexity, and a small example. 1. push() - 0(1) Add a new element to the end of the array.
🌐
Reddit
reddit.com › r/javascript › javascript interview question: flatten an array (in-depth)
r/javascript on Reddit: Javascript Interview Question: Flatten an Array (in-depth)
October 25, 2018 -

Write a function that flattens a given input array. It can contain many deeply nested arrays. Example: given [[1],[[2]],[[[3]]]] the function should return [1,2,3].

I've been studying up on some common JS interview questions and this one really intrigued me. So, I spent a bit of time looking in depth at the problem and would love other's feedback and help to determine if I assessed it correctly. Also, if anyone else has been presented with this problem maybe this will help them clearly look at the trade-offs of iterative vs recursion in this specific situation.

Also, I think I commonly see the answer for time and space complexity to this question as O(n)... but I think that's wrong. See my analysis below.

Thanks in advance for the feedback and I hope this will help others when they need to answer a question like this.

https://gist.github.com/jcarroll2007/4ee72b3e99507c4f8ce3916fca147ab7

Top answer
1 of 5
2

if I were an interviewer, I think I would be satisfied by your answer, but as a fellow programmer I think that despite the current situation with tail call optimisation, at least attempting a tail recursive solution is worth it, because you can 1) be future proof for when the tail call situation improves and 2) use a trampoline to put a tail-call style recursive function into an iterative process in the meantime, which solves the issue.

Here is an example of a tail-recursive flatten function that uses a "trampoline" to put each recursive call into an iterative while loop. It's probably not the most efficient way of going about things, but due to the trampoline, there should be no stack growth issue. I also opted to not "return a continuation" for the continuation part of the trampoline. Returning a continuation function would increase the amount of inner function executions and definitions, so instead I just take a "snapshot" of the arguments and call the original function with those arguments.

const trampoline = fn => (...args) => {
  let step = fn(...args);
  while (!step.done) {
    step = fn(...step.args);
  }
  return step.result;
}

trampoline.done = result => ({ done: true, result });
trampoline.next = (...args) => ({ done: false, args });

const flatten = trampoline((list, accumulator = []) => {
  if (list.length === 0) {
    return trampoline.done(accumulator);
  }
  const [head, ...tail] = list;
  if (Array.isArray(head)) {
    return trampoline.next([...head, ...tail], accumulator);
  }
  return trampoline.next(tail, [...accumulator, head]);
});

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

2 of 5
1

const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));

example:

deepFlatten([1, [2], [[3], 4], 5]); // [1,2,3,4,5]

https://30secondsofcode.org/#deepflatten

Find elsewhere
🌐
Gitbooks
knaidu.gitbooks.io › problem-solving › content › arrays › array_flattening.html
Array Flattening · Problem Solving for Coding interviews
Array result.concat(x) else result << x end end return result end · Test.expect flatten([]) == [] Test.expect flatten([1,2,3]) == [1,2,3] Test.expect flatten([[1,2,3],["a","b","c"],[1,2,3]]) == [1,2,3,"a","b","c",1,2,3] Test.expect flatten([[3,4,5],[[9,9,9]],["a,b,c"]]) == [3,4,5,[9,9,9],"a,b,c"] Test.expect flatten([[[3],[4],[5]],[9],[9],[8],[[1,2,3]]]) == [[3],[4],[5],9,9,8,[1,2,3]]
🌐
Medium
codingwithmanny.medium.com › javascript-flatten-array-algorithm-311c2a714a04
JavaScript Flatten Array Algorithm | by Manny | Medium
April 11, 2020 - While GraphQL from a database could solve this, if we’re dealing with a REST API or we have a ton of data that we need to get through and format a specific way, we might need to flatten the data. You could technically use the new ES2019 functionality called Array.flat which takes an array and just flattens it by making all sub arrays (or multidimensional array) into a single array.
🌐
freeCodeCamp
freecodecamp.org › news › flatten-array-recursion
How to Flatten an Array in JavaScript Using Recursion
August 18, 2022 - If it's a number, push it to our output array and then recurse to the next index. So, the time complexity is going to be nearly exponential. Recursion is rarely used in production environments.
🌐
Medium
medium.com › @inbasekaran18 › understanding-time-complexity-of-array-methods-in-javascript-cc7bb30b3e9d
Understanding Time Complexity of Array Methods in JavaScript | by Inbasekaran | Medium
May 29, 2024 - By converting the array to a `Set`, we reduce the lookup complexity to O(1), leading to an overall complexity of O(n). ... Understanding the time complexity of array methods in JavaScript is essential for writing efficient code.
🌐
Stackademic
blog.stackademic.com › from-nested-to-flat-implementing-polyfill-for-array-flat-in-javascript-d4a8f87241fc
From Nested to Flat: Implementing Polyfill for Array.flat() in JavaScript | by Mansi Manhas | Stackademic
August 3, 2024 - The above polyfill recursively processes each element in the input array. If an element is an array, it drills down into that nested array, flattens it, and then collects the flattened elements into the results array.
🌐
Medium
medium.com › @kruthiv › javascript-series-arrays-part-4-8cf0332b7ef8
JavaScript Series: Arrays Part — 3 | by Kruthi Venkatesh | Medium
November 3, 2023 - Accessing elements in a JavaScript array is straightforward. Below, I will discuss how to access array elements, along with common operations associated with arrays and its time, space complexities.
🌐
Medium
medium.com › @pivajr › pythonic-tips-mastering-list-flattening-in-python-409b731d4c9a
Pythonic Tips: Mastering List Flattening in Python | by Dilermando Piva Junior | Medium
April 24, 2025 - At Olist, a Brazilian marketplace, engineers used recursive flattening to process order data with up to 7 levels of nesting, reducing report processing time from 2 hours to just 15 minutes.
🌐
GeeksforGeeks
geeksforgeeks.org › javascript-array-flat-method
JavaScript Array flat() Method | GeeksforGeeks
July 12, 2024 - The Javascript arr.flat() method was introduced in ES2019. The flat() method in JavaScript creates a new array with all sub-array elements concatenated into it recursively up to the specified depth.
🌐
ammar mian
ammarmian.wordpress.com › 2016 › 05 › 09 › recursive-solutions-to-flattening-an-array
Recursive Solutions to Flattening an Array – ammar mian
May 17, 2016 - Conquer: This portion takes a time complexity of O(k), as we are simply concatenating k complementary subsections until they merge into 1 array.
🌐
Codefinity
codefinity.com › courses › v2 › 212d3d3e-af15-4df9-bb13-5cbbb8114954 › 58324ed0-9644-4e88-ba8c-e93d15b8697a › c72b87c4-3da6-41f0-9133-559b82b95adc
Learn Basic Array Operations Time Complexity | List and Array
As we discussed previously and as it can be seen from the above, the main advantage of the array is that we can access an arbitrary item in constant time if we know the index of that item in an array. But if we don't know what item we are looking for, the lookup operation takes O(N) time complexity as we must provide a Linear Search for an element.
🌐
Stack Overflow
stackoverflow.com › questions › 74239026 › how-to-flat-an-array-of-arrays-and-order-it-in-javascript
How to flat an array of arrays and order it in javascript - Stack Overflow
I think maybe with reduce I can both flat the array and order it? I'm trying to get a better performance at my algorithm ... There's not really a better way to flatten and sort integers; the only way to get better time complexity is to use a different sorting algorithm, since the default Javascript algorithm is merge sort, which has O(nlogn) complexity.