6 min read
Jun 6
Stop using arrays for everything in JavaScript. Learn why arrays can hurt performance and clarity in large-scale apps, and discover better alternatives like Set, Map, and LinkedList—with clear, practical code examples.
Share this article on
The content provided in this article is based solely on my research and personal understanding. While I strive for accuracy, information may vary, and readers should verify details independently.
If you wish to redistribute or reference this article, please ensure you provide a proper backlink to the original source.
Thank you for your understanding and support!
Subscribe now to get expert insights, coding tips, and exclusive content delivered straight to your inbox!
By subscribing, you consent to receiving emails from The Cypher Hub
Arrays Are Not Always the Answer: Choosing the Right Data Structure in JavaScript
Arrays are often the first data structure we learn in JavaScript. They’re simple ([]
), syntactically convenient, and packed with built-in methods like .push()
, .pop()
, .filter()
, .map()
, and .reduce()
. Early on, we reach for arrays for nearly every problem because they’re familiar, easy to create, and seem to solve most basic tasks.
However, using arrays as a catch-all can result in:
Performance pitfalls when operations like membership checks, middle deletions, or front removals become O(n).
Semantic confusion—is this array a Set, a Stack, a Queue, or just a bag of items?
Scalability headaches, since code that works on small arrays may buckle under large data volumes.
Below, we’ll explore key trade-offs, modern ES6 alternatives, and concise code snippets so you know when to reach for Sets, Maps, linked lists, and more.
Performance Pitfalls
Membership checks using includes()
run in O(n) each time.
Queue simulations with shift()
suffer O(n) re-indexing on removal.
Middle insertions/deletions via splice()
also take O(n).
Semantic Confusion
When you see:
const data = [];
data.push(item);
data.includes(item);
data.splice(index, 1);
...is that a Set, a Stack, a Queue, or just an untyped list? Intent gets lost.
Scalability Issues
What works fast for 10 or 100 items can slow dramatically at 10,000+. Refactoring array-heavy logic is error-prone.
A Set
stores unique values with average O(1) for .add()
, .has()
, and .delete()
.
const arr = [1, 2, 2, 3, 3, 3, 4];
const unique = [...new Set(arr)]; // [1,2,3,4]
const whitelist = new Set(["alice", "bob", "charlie"]);
function isAllowed(name) {
return whitelist.has(name); // O(1) lookup
}
When to use: you only care about membership or uniqueness. Great for filtering duplicates and whitelists.
A Map
functions like an object but:
Accepts any key type (primitives or objects).
Preserves insertion order.
Exposes a .size
property.
const text = "hello world hello hello world";
const counts = new Map();
text.split(" ").forEach(word => {
counts.set(word, (counts.get(word) || 0) + 1);
});
for (const [word, freq] of counts) {
console.log(word, freq);
}
// hello 3
// world 2
When to use: you need a true key/value store with complex keys, ordered iteration, or constant-time size checks.
Plain Objects {}
are best for simple string-keyed data and JSON serialization.
Maps shine when you need non-string keys, guaranteed iteration order, and efficient .size
lookups.
Arrays incur O(n) for middle insertions and deletions. With a linked list, you pay O(1) for these operations if you hold a node reference—at the cost of extra pointer storage and no direct random access.
Singly Linked List (O(1) append):
class ListNode {
constructor(val) { this.val = val; this.next = null; }
}
class SinglyLinkedList {
constructor() { this.head = this.tail = null; }
append(val) {
const node = new ListNode(val);
if (!this.head) this.head = node;
else this.tail.next = node;
this.tail = node;
}
}
Use case: stacks, queues, or adjacency lists where you frequently add or remove nodes at known positions.
Using an array’s .push()
and .pop()
is fine for small-scale stacks:
const stack = [];
stack.push(1);
stack.push(2);
console.log(stack.pop()); // 2
But if you want clear intent or a pluggable backend:
class Stack {
constructor() { this.data = []; }
push(x) { this.data.push(x); }
pop() { return this.data.pop(); }
peek() { return this.data[this.data.length - 1]; }
}
Avoid shift()
on arrays for large queues—.shift()
is O(n). Instead, back with a linked list:
class Queue {
constructor() { this.head = this.tail = null; }
enqueue(val) {
const node = { value: val, next: null };
if (!this.head) this.head = node;
else this.tail.next = node;
this.tail = node;
}
dequeue() {
if (!this.head) return null;
const val = this.head.value;
this.head = this.head.next;
if (!this.head) this.tail = null;
return val;
}
}
When to use: heavy enqueue/dequeue workloads where O(1) operations matter.
Arrays excel at random access (arr[i]
) and end-operations (push
, pop
). Beyond that, consider alternatives:
Set for uniqueness and fast membership.
Map for flexible key/value storage.
Linked List for constant-time middle inserts/deletes.
Stack/Queue classes for clear intent and scalable behavior.
By understanding these trade-offs, you’ll write faster code, express your intent more clearly, and build systems that scale without painful refactors. Next time you reach for const data = []
, pause and ask: Is there a better data structure for this job?
Happy coding!
This article was last updated on Jun 8
No comments found.
Stop using arrays for everything in JavaScript. Learn why arrays can hurt performance and clarity in large-scale apps, and discover better alternatives like Set, Map, and LinkedList—with clear, practical code examples.
6 min read
Jun 6
Discover how WhatsApp powers real-time video calling for over 2 billion users worldwide. Learn about P2P architecture, WebRTC, encryption, network optimizations, and the engineering behind seamless communication.
5 min read
Apr 28
You’re late for a meeting. You step outside, open the inDrive app, type in your destination, and hit "Request a ride." Within seconds, your phone buzzes — a driver is on the way. Seems simple, right? But behind that seamless experience is a high-performance, real-time system capable of handling thousands of simultaneous ride requests across hundreds of cities. In this article, we’ll take a situational deep dive into how inDrive likely finds nearby drivers so fast, breaking down the key tech stack, algorithms, and real-time architecture that powers the magic. Let’s simulate what happens the moment you tap that request button. Absolutely let’s unpack that entire process in deep technical detail, layer by layer, and walk through each component in the chain from the moment the user taps “Request” on the inDrive app.
6 min read
Apr 15
Subscribe now to get expert insights, coding tips, and exclusive content delivered straight to your inbox!
By subscribing, you consent to receiving emails from The Cypher Hub