179

I have a model with possibly thousands of objects. I was wondering what would be the most efficient way of storing them and retrieving a single object once I have it's id. The id's are long numbers.

So these are the 2 options I was thinking about. in option one it's a simple array with an incrementing index. in option 2 it's an associative array and maybe an object, if it makes a difference. My question is which one is more efficient, when I mostly need to retrieve a single object, but also sometimes loop through them and sort.

Option one with non associative array:

var a = [{id: 29938, name: 'name1'},
         {id: 32994, name: 'name1'}];
function getObject(id) {
    for (var i=0; i < a.length; i++) {
        if (a[i].id == id) 
            return a[i];
    }
}

Option two with associative array:

var a = [];  // maybe {} makes a difference?
a[29938] = {id: 29938, name: 'name1'};
a[32994] = {id: 32994, name: 'name1'};
function getObject(id) {
    return a[id];
}

Update:

OK, I get that using an array in the second option is out of the question. So the declaration line the second option should really be: var a = {}; and the only question is: what is performing better in retrieving an object with a given id: an array or an object where the id is the key.

and also, will the answer change if i will have to sort the list many times?

clemens
  • 16,716
  • 11
  • 50
  • 65
Moshe Shaham
  • 15,448
  • 22
  • 74
  • 114
  • 1
    this helps may be:: http://stackoverflow.com/questions/13309464/javascript-big-arrays-or-objects-browser-performance-and-memory – Sudhir Bastakoti Jun 25 '13 at 10:38
  • Do you need a sorted collection at all times? If so, there is no other option than an array (although not using the indexes like you currently do). – Jon Jun 25 '13 at 10:38
  • @Jon actually, I do. what do you mean by "like you currently do"? – Moshe Shaham Jun 25 '13 at 10:40
  • 1
    @MosheShaham: Arrays (should) have continuous indexes starting from 0. If you use arrays, don't do anything else. – Jon Jun 25 '13 at 10:52
  • 1
    I guess this benchmark will answer the first part of your question: http://jsben.ch/#/Y9jDP – EscapeNetscape Oct 20 '16 at 11:27
  • This question was asked almost ten years ago, but now you could technically use web assembly for performance optimizations. – Anatoly Dec 28 '21 at 17:17

8 Answers8

175

The short version: Arrays are mostly faster than objects. But there is no 100% correct solution.

Update 2017 - Test and Results

var a1 = [{id: 29938, name: 'name1'}, {id: 32994, name: 'name1'}];

var a2 = [];
a2[29938] = {id: 29938, name: 'name1'};
a2[32994] = {id: 32994, name: 'name1'};

var o = {};
o['29938'] = {id: 29938, name: 'name1'};
o['32994'] = {id: 32994, name: 'name1'};

for (var f = 0; f < 2000; f++) {
    var newNo = Math.floor(Math.random()*60000+10000);
    if (!o[newNo.toString()]) o[newNo.toString()] = {id: newNo, name: 'test'};
    if (!a2[newNo]) a2[newNo] = {id: newNo, name: 'test' };
    a1.push({id: newNo, name: 'test'});
}

test setup test results

Original Post - Explanation

There are some misconceptions in your question.

There are no associative arrays in Javascript. Only Arrays and Objects.

These are arrays:

var a1 = [1, 2, 3];
var a2 = ["a", "b", "c"];
var a3 = [];
a3[0] = "a";
a3[1] = "b";
a3[2] = "c";

This is an array, too:

var a3 = [];
a3[29938] = "a";
a3[32994] = "b";

It's basically an array with holes in it, because every array does have continous indexing. It's slower than arrays without holes. But iterating manually through the array is even slower (mostly).

This is an object:

var a3 = {};
a3[29938] = "a";
a3[32994] = "b";

Here is a performance test of three possibilities:

Lookup Array vs Holey Array vs Object Performance Test

An excellent read about these topics at Smashing Magazine: Writing fast memory efficient JavaScript

aleclarson
  • 18,087
  • 14
  • 64
  • 91
Alp
  • 29,274
  • 27
  • 120
  • 198
  • in your test, it tells me that Holey Array is the fastest (120K vs. 92K for object). does that make sense? – Moshe Shaham Jun 25 '13 at 11:13
  • @Moshe This very much depends on the browser. See the browser comparison at the end of the page. – deceze Jun 25 '13 at 11:14
  • 1
    @Moshe And thereby all discussion about performance in Javascript should be done. :P – deceze Jun 25 '13 at 11:18
  • If there is no huge difference between the results, just stick to the most maintainable and eleganz solution. If you see significant performance differences, stick to the fastest lookup technique. This applies for almost every aspect of your script. – Alp Jun 25 '13 at 11:31
  • @Alp OK, I hear you. I thought I'd ask before I code because if I choose array it will be a serious pain to change it... – Moshe Shaham Jun 25 '13 at 11:36
  • 12
    This really depends on the data and size of the data you are working with. Very small data sets and small objects will perform much better with arrays. If your talking about lookup's in a large data set where you use a object as a map then a object is more efficient. http://jsperf.com/array-vs-object-performance/35 – f1v Feb 14 '14 at 17:07
  • 5
    Agree with f1v, but Revision 35 has a flaw in the test: `if (a1[i].id = id) result = a1[i];` Should be: `if (a1[i].id === id) result = a1[i];` Test [http://jsperf.com/array-vs-object-performance/37](http://jsperf.com/array-vs-object-performance/37) corrects that – Charles Byrne Jul 01 '14 at 12:39
  • 1
    See [http://jsperf.com/array-vs-object-performance/71](http://jsperf.com/array-vs-object-performance/71). Has a smaller subset of data (I should have looped for data creation, but I wanted holes in the array) of about 93 objects compared to 5000. Outer loop are Ids to search for scattered in the object array (beginning middle and end) and I added a missing id as well so the Array lookup would have to traverse all elements. Holey Array, the Object by Key, then Manual Array. So as f1v stated it really depends on the size of the data and where the data is for manual array searches. – Charles Byrne Jul 01 '14 at 13:34
  • 1
    First you say that there is no such thing as associative arrays, and then you say, it has "lookup arrays" which, I assume, are actually the same thing. Please re-read [Writing Fast, Memory-Efficient JavaScript](http://www.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/) by Google's [Addy Osmani](https://speakerdeck.com/addyosmani). The very article you linked contradicts this your first boldface point. An object has two general purposes: 1. An instance of a `hidden class` and 2. a "dictionary", "associative array", or whatever you call it. – Domi Sep 12 '14 at 09:39
  • Those lookup arrays i talked of is a manual approach of finding the correct index. Besides, thanks for your comment, you are right about those points! – Alp Sep 12 '14 at 10:20
  • 1
    http://jsperf.com/array-vs-object-performance/182 shows, as expected, that the linear lookup performance is vanishingly small compared to indexed/keyed lookup, on data sets where performance is going to matter anyway (i.e. not a test size of 2 entries). In this case the difference between Holey Array and Object by Key appears to be negligible. I'd be curious about memory size however, as each object entry presumably carries a string property which is not gc'able. – aaron Apr 05 '15 at 22:17
  • 1
    there are arrays? var a = [1,2,3], b = {'1':1,'2':2}; typeof a == 'object' typeof b == 'object' ? – Guntram Jul 09 '15 at 14:35
  • You are right @Guntram, internally both have the same type. But syntactically they have different prototypes and functionalities. Therefore the performance differences. – Alp Jun 09 '16 at 06:20
  • 5
    This answer could be improved by summarizing the jsPerf conclusions here in this post - especially since the jsPerf results are the real answer to the question. The rest is extra. This is more relevant in times when jsPerf is down (like right now). http://meta.stackexchange.com/questions/8231/are-answers-that-just-contain-links-elsewhere-really-good-answers – Jeff Jul 22 '16 at 18:03
  • When I use `find()` on Array i get the same performance than with Holey Array or Object (at least with Chrome) http://jsben.ch/#/05cmA – Supersharp Apr 10 '17 at 10:23
  • added update with screenshots for test setup and results – Alp Apr 10 '17 at 11:02
  • 1
    This is a little improvement adding `find()` to the array http://jsben.ch/Bjbf9 Conclusión if the searched element is the first one same as object if not, as long as the far it is. – pikilon Sep 05 '17 at 09:38
  • 2
    The test is flawed. The "array" approach in reality is not that slow. **Firstly**, when generating elements, `o` and `a2` get a new element _only if they don't already have it_, while a new one is pushed in `a1` _always_. If it generates the same number twice, it won't be added to `o` and `a2`, but will be pushed in `a1`. Unlikely, but still... **Secondly**, in the test of `a1`, any normal person would break the loop once they find the item... this significantly changes the results. [Check for yourself](http://jsben.ch/j9X4n). – dodov Sep 09 '17 at 20:08
  • 1
    Results change based on browser. The margin error between associative array and object is too small in chrome 61.0.3163.100. In Firefox 56.0, array is close to associate array's speed and object is clearly the fastest. In edge, object is clearly the fastest. I did discover that the array.find method is incredibly slow in Firefox but matches the performance of array in the other browsers. – SILENT Oct 05 '17 at 23:35
  • It seems to be faster intermittently – olfek Sep 11 '18 at 14:23
  • It all comes down to the difference between address, linear, binary and hash lookups. – martian17 Feb 23 '19 at 20:54
  • For big data sizes, address(O(1)) > hash(O(1)) >> binary(O(logn)) >> linear(O(n)). Array should be faster than object. – martian17 Feb 23 '19 at 21:01
  • perhaps this is a minor nitpick, but would it be (at least slightly) faster to move `a1.length` to a variable outside the for loop? –  May 19 '20 at 23:00
  • I am sure you need to check with different sizes of data. – Zhong Ri Jun 25 '20 at 13:34
  • Has anyone checked how binary searching a sorted array of objects fits into this? Also jsperf is down ;-( – Indiana Kernick Jan 06 '21 at 01:40
  • Is it just me thinking the "short version" is miswritten? How is an array faster? The tests show (and algo background too) that object is much faster (on significatly big data). – vargen_ Mar 12 '21 at 14:43
  • 1
    @Alp Adding test results for ES6 Map() would also be beneficial. – Farzan Jun 14 '21 at 23:34
  • When I click on the first link in the accepted answer. It brings me to like a virus website, if this happens to anyone else please comment it here and if so link should be changed. https://www.cleaninstall.autos/82850008-fc57-4ace-82a9-fce2eced1bca/?btd=dHJrLnNoaW5lLWNhci1kZXNlcnQtYWRqZWN0aXZlLnh5eg&exptoken=MTY2NTcyMDQ5MDg4MA%3D%3D&lang=en&r_brand=Motorola&r_model=Moto+Z4&td=dHJrLnNvbHV0aW9uLW5hdGlvbmFsLXBhY2stcmVtYXJrYWJsZS54eXovc253ZGFydGY – Wayne Oct 14 '22 at 04:08
  • Just checked both links and they work as expected. Might be something on your computer? – Alp Oct 17 '22 at 12:18
  • I think the code for testing the array performance do need add `break;` after the result is found. The code proposed above is looping every single object even the result is found. – PlusA May 10 '23 at 03:38
29

It's not really a performance question at all, since arrays and objects work very differently (or are supposed to, at least). Arrays have a continuous index 0..n, while objects map arbitrary keys to arbitrary values. If you want to supply specific keys, the only choice is an object. If you don't care about the keys, an array it is.

If you try to set arbitrary (numeric) keys on an array, you really have a performance loss, since behaviourally the array will fill in all indexes in-between:

> foo = [];
  []
> foo[100] = 'a';
  "a"
> foo
  [undefined, undefined, undefined, ..., "a"]

(Note that the array does not actually contain 99 undefined values, but it will behave this way since you're [supposed to be] iterating the array at some point.)

The literals for both options should make it very clear how they can be used:

var arr = ['foo', 'bar', 'baz'];     // no keys, not even the option for it
var obj = { foo : 'bar', baz : 42 }; // associative by its very nature
deceze
  • 510,633
  • 85
  • 743
  • 889
  • I don't want to supply specific keys. I want to know what is performing better, and I will work with that. OK, so in the second option an array is out of the question. but what about an object vs. the non associative array? – Moshe Shaham Jun 25 '13 at 10:46
  • 1
    @Moshe There's no such thing as a non-associative array in Javascript. If you need keys (numbers or strings), use an object. If you just need an (ordered) list, use arrays. Period. Performance doesn't enter the discussion. If performance is crucial and you could live with your keys either way, try which one works better for you. – deceze Jun 25 '13 at 10:52
  • 7
    But I want to know what is performing better: retrieving an object from an array (by looping through it) or from an "associative" object where the id is the key. I'm sorry if my question wasn't clear... – Moshe Shaham Jun 25 '13 at 10:57
  • 2
    @Moshe If you access anything by key, either in an object or array, it's always going to be infinitely faster than looping through the container trying to find what you want. The difference of accessing an item by key in an array or in an object is likely negligible. Looping is obviously worse either way. – deceze Jun 25 '13 at 10:59
  • 1
    @deceze — How "about array holding user objects and to get the object of the user, a loop is needed to get user object based on `user_id`" vs "object having keys as `user_id` hence user object could be accessed using `user_id` as key" ? Which one is better in terms of the performance ? Any suggestions on this are appreciated :) – Rayon Sep 09 '16 at 11:12
14

With ES6 the most performant way would be to use a Map.

var myMap = new Map();

myMap.set(1, 'myVal');
myMap.set(2, { catName: 'Meow', age: 3 });

myMap.get(1);
myMap.get(2);

You can use ES6 features today using a shim (https://github.com/es-shims/es6-shim).

Performance will vary depending on the browser and scenario. But here is one example where Map is most performant: https://jsperf.com/es6-map-vs-object-properties/2


REFERENCE https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map

sandstrom
  • 14,554
  • 7
  • 65
  • 62
9

In NodeJS if you know the ID, the looping through the array is very slow compared to object[ID].

const uniqueString = require('unique-string');
const obj = {};
const arr = [];
var seeking;

//create data
for(var i=0;i<1000000;i++){
  var getUnique = `${uniqueString()}`;
  if(i===888555) seeking = getUnique;
  arr.push(getUnique);
  obj[getUnique] = true;
}

//retrieve item from array
console.time('arrTimer');
for(var x=0;x<arr.length;x++){
  if(arr[x]===seeking){
    console.log('Array result:');
    console.timeEnd('arrTimer');
    break;
  }
}

//retrieve item from object
console.time('objTimer');
var hasKey = !!obj[seeking];
console.log('Object result:');
console.timeEnd('objTimer');

And the results:

Array result:
arrTimer: 12.857ms
Object result:
objTimer: 0.051ms

Even if the seeking ID is the first one in the array/object:

Array result:
arrTimer: 2.975ms
Object result:
objTimer: 0.068ms
Paweł
  • 4,238
  • 4
  • 21
  • 40
7

I tried to take this to the next dimension, literally.

Given a 2 dimensional array, in which the x and y axes are always the same length, is it faster to:

a) look up the cell by creating a two dimensional array and looking up the first index, followed by the second index, i.e:

var arr=[][]    
var cell=[x][y]    

or

b) create an object with a string representation of the x and y coordinates, and then do a single lookup on that obj, i.e:

var obj={}    
var cell = obj['x,y']    

Result:
Turns out that it's much faster to do two numeric index lookups on the arrays, than one property lookup on the object.

Results here:

http://jsperf.com/arr-vs-obj-lookup-2

AVI
  • 5,516
  • 5
  • 29
  • 38
Davem M
  • 477
  • 7
  • 9
4

It depends on usage. If the case is lookup objects is very faster.

Here is a Plunker example to test performance of array and object lookups.

https://plnkr.co/edit/n2expPWVmsdR3zmXvX4C?p=preview

You will see that; Looking up for 5.000 items in 5.000 length array collection, take over 3000 milisecons

However Looking up for 5.000 items in object has 5.000 properties, take only 2 or 3 milisecons

Also making object tree don't make huge difference

Mehmet Otkun
  • 1,374
  • 9
  • 22
1

I had a similar problem that I am facing where I need to store live candlesticks from an event source limited to x items. I could have them stored in an object where the timestamp of each candle would act as the key and the candle itself would act as the value. Another possibility was that I could store it in an array where each item was the candle itself. One problem about live candles is that they keep sending updates on the same timestamp where the latest update holds the most recent data therefore you either update an existing item or add a new one. So here is a nice benchmark that attempts to combine all 3 possibilities. Arrays in the solution below are atleast 4x faster on average. Feel free to play

"use strict";

const EventEmitter = require("events");
let candleEmitter = new EventEmitter();

//Change this to set how fast the setInterval should run
const frequency = 1;

setInterval(() => {
    // Take the current timestamp and round it down to the nearest second
    let time = Math.floor(Date.now() / 1000) * 1000;
    let open = Math.random();
    let high = Math.random();
    let low = Math.random();
    let close = Math.random();
    let baseVolume = Math.random();
    let quoteVolume = Math.random();

    //Clear the console everytime before printing fresh values
    console.clear()

    candleEmitter.emit("candle", {
        symbol: "ABC:DEF",
        time: time,
        open: open,
        high: high,
        low: low,
        close: close,
        baseVolume: baseVolume,
        quoteVolume: quoteVolume
    });



}, frequency)

// Test 1 would involve storing the candle in an object
candleEmitter.on('candle', storeAsObject)

// Test 2 would involve storing the candle in an array
candleEmitter.on('candle', storeAsArray)

//Container for the object version of candles
let objectOhlc = {}

//Container for the array version of candles
let arrayOhlc = {}

//Store a max 30 candles and delete older ones
let limit = 30

function storeAsObject(candle) {

    //measure the start time in nanoseconds
    const hrtime1 = process.hrtime()
    const start = hrtime1[0] * 1e9 + hrtime1[1]

    const { symbol, time } = candle;

    // Create the object structure to store the current symbol
    if (typeof objectOhlc[symbol] === 'undefined') objectOhlc[symbol] = {}

    // The timestamp of the latest candle is used as key with the pair to store this symbol
    objectOhlc[symbol][time] = candle;

    // Remove entries if we exceed the limit
    const keys = Object.keys(objectOhlc[symbol]);
    if (keys.length > limit) {
        for (let i = 0; i < (keys.length - limit); i++) {
            delete objectOhlc[symbol][keys[i]];
        }
    }

    //measure the end time in nano seocnds
    const hrtime2 = process.hrtime()
    const end = hrtime2[0] * 1e9 + hrtime2[1]

    console.log("Storing as objects", end - start, Object.keys(objectOhlc[symbol]).length)
}

function storeAsArray(candle) {

    //measure the start time in nanoseconds
    const hrtime1 = process.hrtime()
    const start = hrtime1[0] * 1e9 + hrtime1[1]

    const { symbol, time } = candle;
    if (typeof arrayOhlc[symbol] === 'undefined') arrayOhlc[symbol] = []

    //Get the bunch of candles currently stored
    const candles = arrayOhlc[symbol];

    //Get the last candle if available
    const lastCandle = candles[candles.length - 1] || {};

    // Add a new entry for the newly arrived candle if it has a different timestamp from the latest one we storeds
    if (time !== lastCandle.time) {
        candles.push(candle);
    }

    //If our newly arrived candle has the same timestamp as the last stored candle, update the last stored candle
    else {
        candles[candles.length - 1] = candle
    }

    if (candles.length > limit) {
        candles.splice(0, candles.length - limit);
    }

    //measure the end time in nano seocnds
    const hrtime2 = process.hrtime()
    const end = hrtime2[0] * 1e9 + hrtime2[1]


    console.log("Storing as array", end - start, arrayOhlc[symbol].length)
}

Conclusion 10 is the limit here

Storing as objects 4183 nanoseconds 10
Storing as array 373 nanoseconds 10
PirateApp
  • 5,433
  • 4
  • 57
  • 90
1
  1. Indexed fields (fields with numerical keys) are stored as a holy array inside the object. Therefore lookup time is O(1)

  2. Same for a lookup array it's O(1)

  3. Iterating through an array of objects and testing their ids against the provided one is a O(n) operation.

badunius
  • 61
  • 1
  • 5