Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 2

Data Structure Trade-Offs: Arrays, Linked Lists, and Hash Maps

Recommended resources

In our previous lesson, we established a formal language for analyzing algorithm efficiency using Big-O notation. Now, we'll put that tool to immediate use. This lesson moves from the abstract analysis of algorithms to the concrete trade-offs of the data structures that underpin them. As an engineering manager, your teams make these foundational choices every day, and understanding their deep implications is key to guiding sound architectural decisions.

Today, we will compare three of the most fundamental linear data structures: arrays, linked lists, and hash maps. Our goal is not just to define them, but to dissect their performance characteristics for common operations like access, search, insertion, and deletion. By the end, you'll be able to confidently choose the right structure for a given problem based on its specific demands.

Arrays: The Power of Contiguity

Arrays are the bedrock of many data structures. Their defining characteristic is that they store elements in a contiguous block of memory. This simple physical layout has profound performance implications.

The primary strength of an array is its ability to access any element by its index in constant time, or . This is possible because the computer can calculate the exact memory address of an element directly. If the array starts at memory address B, and each element takes up S bytes, the address of the element at index i is simply B + i * S. There's no need to search; it's a direct computation.

However, this rigid structure is also their main weakness.

Data Structures Tutorial: Complete Guide for Beginners (2025)

This section of the "Data Structures Tutorial" provides a concise summary of array characteristics and performance.

Please read the section titled "Arrays: The Foundation of Data Structures". Pay close attention to the explanation for why insertion and deletion are O(n) operations.

As the reading highlights, inserting or deleting an element anywhere other than the end of an array is an operation. To insert at the beginning, you must shift every existing element one position to the right to make space. To delete from the beginning, you must shift every subsequent element one position to the left to fill the gap. This cost can be prohibitive in applications with frequent modifications.

Most modern languages use dynamic arrays (like Python's list or Java's ArrayList), which automatically resize when they run out of space. While appending to the end is often described as , it's more accurately amortized . When the array is full, a new, larger array is allocated, and all existing elements are copied over—an operation. However, because this happens infrequently, the average cost over many appends is constant.

When to use an Array:

  • When you need fast, indexed access to elements.
  • When the number of elements is relatively static or known upfront.
  • When you're primarily reading or iterating over data, with few insertions or deletions in the middle.

Linked Lists: Flexibility Through Pointers

Linked lists offer a trade-off: they sacrifice fast indexed access for efficient insertions and deletions. Instead of a contiguous memory block, a linked list is a chain of nodes. Each node contains data and a pointer (or reference) to the next node in the sequence.

This video from mycodeschool offers a brilliant and classic comparison of arrays and linked lists.

Data Structures: Arrays vs Linked Lists

This video directly compares arrays and linked lists on the costs of access, insertion, and deletion.

Watch the following two segments: Cost of Access & Memory: This section explains why access is O(1) for arrays but O(n) for linked lists, and discusses the differences in memory layout. Cost of Insertion/Deletion: This part breaks down the performance trade-offs for modifying the data structures at the beginning, end, and middle.

As the video explains, accessing an element at a specific position in a linked list is an operation, because you must traverse the list from the head node one by one. This is their single biggest disadvantage compared to arrays.

Their strength lies in modification. To add or remove an element at the beginning of the list, you only need to update a few pointers—an operation. There is no need to shift other elements.

There are two main variants:

  • Singly Linked List: Each node points only to the next node. Inserting at the head is , but adding to the end is because you must first traverse the entire list to find the last node.
  • Doubly Linked List: Each node points to both the next and the previous node. If you also maintain a pointer to the tail of the list, you can perform insertions and deletions at both ends in time. This makes them ideal for implementing structures like queues and deques.

Data Structures in JavaScript: Arrays, HashMaps, and Lists

This article provides a detailed implementation-level view of both singly and doubly linked lists.

Please review the following sections to solidify the concepts: Skim through the implementation details of a singly linked list to see how addFirst is O(1) while addLast is O(n). Then, read about the improvements offered by doubly linked lists, which make operations at both ends O(1).

When to use a Linked List:

  • When you have frequent insertions and deletions, especially at the beginning or end of the sequence.
  • When you don't know the number of elements in advance and need the structure to grow and shrink dynamically without large reallocation costs.
  • When implementing other data structures like stacks, queues, and deques.

Hash Maps: The Magic of Key-Value Lookups

What if you don't care about the order of elements, but you need to look them up extremely quickly based on a unique identifier? Neither arrays (which search in ) nor linked lists (also search) are a good fit. This is the problem that hash maps (also known as hash tables or dictionaries) are designed to solve.

A hash map is a key-value store that provides, on average, time complexity for insertion, deletion, and search. This remarkable performance comes from a clever combination of an array and a hash function.

The following video provides a simple and clear mental model for how hash maps work internally.

HashMaps & Dictionaries, Explained Simply

This video explains the "why" and "how" of hash maps, from the initial problem to the details of collision resolution.

Watch these key parts to build your intuition: The Problem & The Solution: This sets up why we need something better than an array for key-based lookups. The Hashing Mechanism: Explains how a hash function converts a key into an array index and introduces the critical concept of collisions. Closed Addressing (Chaining): This covers the most common strategy for handling collisions, using linked lists inside the backing array.

Here's the process in a nutshell:

  1. To store a (key, value) pair, the hash map passes the key to a hash function, which produces a large integer hash code.
  2. This hash code is then mapped to an index in a backing array (typically using the modulo operator: index = hash_code % array_length).
  3. The value is stored at that index.

The crucial problem is collisions: what happens when two different keys produce the same index? As the video explains, the most common solution is chaining, where each slot in the backing array is a bucket that can hold multiple values, often using a linked list. When a collision occurs, the new item is simply added to the linked list at that index.

Performance Trade-offs:

  • Average Case : If the hash function distributes keys evenly and the backing array is reasonably sized (i.e., the "chains" are short), looking up an element involves a hash calculation and accessing an array element—all constant time operations.
  • Worst Case : If the hash function is poor or you're exceptionally unlucky, all keys could map to the same index. The hash map then degenerates into a single linked list, and search becomes . This is why the choice of hash function and a strategy for resizing the map (based on a "load factor") are critical for real-world implementations.

When to use a Hash Map:

  • When you need to store and retrieve data by a unique key.
  • When insertion and lookup speed are critical.
  • When the order of elements does not matter.
  • Common use cases: implementing caches, counting frequencies of items, indexing database records.

Summary of Trade-offs

This is a lot to take in, so let's summarize the performance characteristics in a table. This cheat sheet is an invaluable reference for comparing data structures at a glance.

This table summarizes the average and worst-case time complexities for the four main operations (Access, Search, Insertion, Deletion) across arrays, linked lists, and hash tables. Note the distinction between average O(1) and worst-case O(n) for hash tables.

Now, let's solidify this with a practical thought exercise. For each scenario, decide which data structure would be the most appropriate choice and why.

  1. Implementing a web browser's history: Users need to move back and forth between recently visited pages. New pages are always added to the "front".

    • Structure: A doubly linked list. It excels at adding/removing from both ends in time, perfectly modeling the "back" and "forward" navigation.
  2. Storing a large dictionary of English words for a spell checker: You need to very quickly check if a given word exists in the dictionary.

    • Structure: A hash map (or a variant called a hash set). It provides average lookup time, which is ideal for quickly verifying the existence of a word.
  3. Managing the pixels on a screen: You need to be able to instantly read or change the color of any pixel given its (x, y) coordinates.

    • Structure: A 2D array. The coordinates map directly to array indices, providing the necessary random access for both reading and writing pixel data.

Conclusion

Today we've moved from theoretical complexity analysis to the practical application of these concepts on fundamental data structures. Your role as a manager often involves weighing these exact trade-offs during design reviews and architectural planning.

Let's recap the core ideas:

  • Arrays are for fast, indexed access. They are memory-efficient but costly to modify in the middle.
  • Linked Lists are for fast insertions and deletions at the ends. They are flexible in size but slow for searching and indexed access.
  • Hash Maps are for near-instantaneous key-based lookups, insertions, and deletions. They are incredibly versatile but come with a performance dependency on a good hash function and have an worst-case scenario.

No single data structure is universally "best." The right choice is always context-dependent, balancing the frequency of operations, memory constraints, and performance requirements of the specific problem you are solving.

In our next lesson, we will build upon this foundation by exploring non-linear data structures: trees, heaps, and graphs. These structures allow us to model more complex hierarchical relationships and networks, unlocking a new class of problems to solve efficiently.