Data Structures In Frontend JavaScript In The Real World (With React Code Examples)
Tags: react, javascript, webdev, frontend
Published: 2022-11-18 12:42:15

---
description: Never seen a linked list in frontend JavaScript code? Me neither. But here are real-world examples of data structures Map, Set, Stack, Queue, and Tree.
---

Data structures can be intimidating. Especially for the self-taught folks among us. If you already tried to study data structures in JavaScript you know what typically happens:

1. A bunch of CS theory is thrown at you.
2. You read pages of code that manually implement e.g. a linked list.
3. All the examples are abstract using `foo` and `bar` or `cats` and `dogs`.

On top of that, you’re confronted with smart-looking diagrams like this:

![Confusing diagram of a tree data structure.](https://media.graphassets.com/4EvGSy80QwamKkduSu6E)

But if you’ve been around for a while you may say “I’ve never seen this in my frontend projects!” And you’re right. I haven’t either.

At the same time, you certainly use some data structures already. Not as obvious as `new HashMap()` but disguised as simple objects or arrays. And if you don’t, this is a good opportunity to get started.

On this page, we’ll 

- answer the question if you should study data structures and when they can be helpful.
- look at a few common data structures that you can find in real-world frontend projects.

No worries, you won’t find full-blown implementations of any data structure here. There are plenty of resources that cover this. But you will see examples that you might recognize from your own code or that can use in your projects or on the job.

## Table Of Contents

1. [Do you have to study data structures as a frontend JavaScript developer?](#do-you-have-to-study-data-structures-as-a-frontend-javascript-developer)
2. [Map](#map)
    1. [Description](#description)
    2. [Real-World Example: Messages with user names](#realworld-example-messages-with-user-names)
3. [Set](#set)
    1. [Description](#description)
    2. [Real-World Example: Keeping track of selected items](#realworld-example-keeping-track-of-selected-items)
4. [Stack](#stack)
    1. [Description](#description)
    2. [Real-World Example: Undo previous actions](#realworld-example-undo-previous-actions)
5. [Queue](#queue)
    1. [Description](#description)
    2. [Real-World Example: Notifications](#realworld-example-notifications)
6. [Tree](#tree)
    1. [Description](#description)
    2. [Real-World Example: Nested menus or comments.](#realworld-example-nested-menus-or-comments)


## Do you have to study data structures as a frontend JavaScript developer?

[This question is likely to trigger a debate](https://www.reddit.com/r/reactjs/comments/muw8c6/should_i_study_data_structures_as_a_react/). Here are the two extremes:

- “Yes. Data structures are essential for any programmer no matter what your tech stack is.”
- “To me all that data structure stuff is theory crap and in my 13+ yrs as a web developer, I have never really seen a case where this stuff would be needed.”

For a lot of devs (especially the self-taught among us), the second answer is much more comforting. And it’s true. At least partly. Here are a few reasons why you won’t find many advanced or formal data structures in JavaScript frontends:

- Data sets on the frontend typically aren’t large enough to see the benefits of advanced data structures.
- [JavaScript doesn’t even support many data structures natively.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures)
- React (as the most widely spread frontend framework) doesn’t work well with custom data structures since it requires immutable state updates. Cloning a custom data structure would likely eat up any performance benefits.

But as I said, the “you don’t need data structures on the frontend” answer is only partly true. 

1. You might need to know data structures for technical interviews (emphasis on might).
2. More importantly, a common problem among Junior frontend devs that leads to really messy code is… **picking the wrong data structure.** [Here is an example.](https://profy.dev/article/react-junior-code-review-and-refactoring-1)

Selecting a data structure can be the make-or-break decision of your code’s quality. It decides what the rendering logic looks like or how you update the data. So it really can be helpful to know a few options.

## Map

### Description

A (hash) map, hash table, or dictionary is basically a key-value storage. In JavaScript we actually use this all the time:

```typescript
{
  key1: "value 1",
  key2: "value 2",
  key3: "value 3",
}
```

We also have a native JavaScript alternative in form of a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). It has a few handy methods and properties (such as `Map.size`), is optimized performance-wise for accessing values by key, and allows any kind of type as keys (even objects).

Another advantage of the Map: [with objects the keys are converted to strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors#property_names) as shown below. A Map on the other hand preserves the original type.

```typescript
const numericKeys = {
  1: "value-1",
  2: "value-2",
}

console.log(Object.keys(numericKeys))
// ["1", "2"]
```

### Real-World Example: Messages with user names

In the wild, you can find maps when two data sets reference each other. As in this chat component.

![](https://media.graphassets.com/1u8LgATq3UyV99nSbWgr)

We have two arrays (messages and users). Each message has a reference to the author via its `userId` field.

```typescript
const messages = [
  {
    id: "message-1",
    text: "Hey folks!",
    userId: "user-1"
  },
  {
    id: "message-2",
    text: "Hi",
    userId: "user-2"
  },
  ...
];

const users = [
  {
    id: "user-1",
    name: "Paul"
  },
  {
    id: "user-2",
    name: "Lisa"
  },
  ...
];
```

We could find the corresponding user when we render each message.

```typescript
messages.map(({ id, text, userId }) => {
  const name = users.find((user) => user.id === userId).name;
  return ... // return JSX
}
```

But that’s not ideal performance-wise as we have to iterate over the `users` array for each message. Instead, we can use a map. 

Let’s start with a simple key-value object. The goal is to create an object like this:

```typescript
{
  "user-1": "Paul",
  "user-2": "Lisa",
  ...
}
```

We can achieve this with the `Array.reduce()` function ([find the complete code here](https://codesandbox.io/s/javascript-data-structures-78k6fe?file=/src/MapExample.jsx)).

```typescript
const messages = [
  {
    id: "message-1",
    text: "Hey folks!",
    userId: "user-1"
  },
  ...
];

const users = [
  {
    id: "user-1",
    name: "Paul"
  },
  ...
];

function ChatUsingMap() {
  const namesById = users.reduce(
    (prev, user) => ({ ...prev, [user.id]: user.name }),
    {}
  );

  return messages.map(({ id, text, userId }) => (
    <div key={id}>
      <div>{text}</div>
      <div>{namesById[userId]}</div>
    </div>
  ));
}
```

Alternatively, we can use a real Map. The constructor accepts an array of key-value pairs, so we can get rid of the reduce function ([here is the code on CodeSandbox](https://codesandbox.io/s/javascript-data-structures-78k6fe?file=/src/SetExample.jsx)).

```typescript
function ChatUsingMap() {
  const namesById = new Map(users.map(({ id, name }) => [id, name]));

  return messages.map(({ id, text, userId }) => (
    <div key={id}>
      <div>{text}</div>
      <div>{namesById.get(userId)}</div>
    </div>
  ));
}
```

For clarity: we pass an array of arrays like this to the Map constructor:

```typescript
[
  ["user-1", "Paul"],
  ["user-2", "Lisa"],
  ...
]
```

[![React on the job - email course](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/skyncbvxln80rmks31wq.png)](https://profy.dev/around-react-email-workshop)

## Set

### Description

A Set is a keyed collection as well that is [natively supported in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). But while it’s easier to think of a Map as an object, a Set is more like an array. It’s important to note that each value in a Set is unique. You can’t add a value twice. [That’s why you may see a Set used to remove duplicates from an array.](https://stackoverflow.com/a/9229821) 

```typescript
const uniqueValues = [...new Set([1, 1, 2, 3, 3])];
// [1, 2, 3]
```

Compared to an array, the main advantage is that you can check if a Set contains a value in a performant way.

### Real-World Example: Keeping track of selected items

In the wild, you can find a set when tracking items selected by a user. Like in this table:

![Example of using a map](no-optimization::https://media.graphassets.com/mQnDnK2dTGmXk8lcmEen)

When the user selects an item we keep track of it by adding its id to a set. When the user unselects we remove it from the set again ([here is the code on CodeSandbox](https://codesandbox.io/s/javascript-data-structures-78k6fe?file=/src/MapExample.jsx)).

```typescript
import { useState } from "react";

const rows = [
  {
    id: "id-1",
    name: "Row 1",
  },
  {
    id: "id-2",
    name: "Row 2",
  },
  ...
];

function TableUsingSet() {
  const [selectedIds, setSelectedIds] = useState(new Set());

  const handleOnChange = (id) => {
    const updatedIdToSelected = new Set(selectedIds);
    if (updatedIdToSelected.has(id)) {
      updatedIdToSelected.delete(id);
    } else {
      updatedIdToSelected.add(id);
    }
    setSelectedIds(updatedIdToSelected);
  };

  return (
    <table>
      <tbody>
        {rows.map(({ id, name }) => (
          <tr key={id}>
            <td>
              <input
                type="checkbox"
                checked={selectedIds.has(id)}
                onChange={() => handleOnChange(id)}
              />
            </td>
            <td>{id}</td>
            <td>{name}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}
```

A final note: using a Set can be a great alternative to using an array. A problematic implementation of the “select items” functionality that you can often see looks something like this:

```typescript
const [selectedRows, setSelectedRows] = useState(
  rows.map((row) => ({ selected: false }))
);

const handleOnChange = (index) => {
  const updatedSelectedRows = [...selectedRows];
  updatedSelectedRows[index] = !updatedSelectedRows[index];
  setSelectedRows(updatedSelectedRows);
};
```

This might look straightforward at first. But accessing the elements via the index can cause problems. Just one example: what happens if the rows can be sorted in a different order? We’d have to keep the `selectedRows` array in sync and sort it the same way.

[If you want to see how migrating to a Set can lead to much cleaner and reduced code check this refactoring session.](https://profy.dev/article/react-junior-code-review-and-refactoring-1)

## Stack

### Description

A basic stack has two features:

- You can add an item to the top of the stack.
- You can remove an item from the top of the stack.

This is called “Last in, first out” (aka LIFO). Might sound complicated but we can implement it with a simple array (as it’s not natively supported in JavaScript). 

```typescript
const stack = [];

// add item to the "top"
stack.push("value");

// remove item from the "top"
const topItem = stack.pop();
```

### Real-World Example: Undo previous actions

In the wild, you can find a stack when you need to keep track of the history of user interactions to build an undo functionality. Like in this table where the user can remove each row.

![Example of undo functionality using a stack](no-optimization::https://media.graphassets.com/eTAGIAuuRVOoxRSCIbKH)

Whenever a user deletes a row from the table we add it to a `history` array (our stack). When the user wants to undo the deletion we get the latest row from the history and re-add it to the table. Some of the functions aren’t shown below but you can find [the complete code here on CodeSandbox](https://codesandbox.io/s/javascript-data-structures-78k6fe?file=/src/StackExample.jsx).

> Note that we can’t just use `Array.push()` and `Array.pop()` as React requires immutable data. Instead, we use `Array.concat()` and `Array.slice()` because both return new arrays.

```typescript
import { useReducer } from "react";

const rows = [
  {
    id: "id-1",
    name: "Row 1",
  },
  {
    id: "id-2",
    name: "Row 2",
  },
  ...
];

// "history" is our stack
const initialState = { rows, history: [] };

function reducer(state, action) {
  switch (action.type) {
    case "remove":
      return {
        rows: removeRow(state, action),
        // Array.concat() as immutable alternative to Array.push()
        history: state.history.concat({
          action,
          row: state.rows[action.index]
        })
      };
    case "undo":
      return {
        rows: addRowAtOriginalIndex(state),
        // Array.slice() as immutable alternative to Array.pope()
        history: state.history.slice(0, -1)
      };
    default:
      throw new Error();
  }
}

function TableUsingStack() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <button
        onClick={() => dispatch({ type: "undo" })}
        disabled={state.history.length === 0}
      >
        Undo Last Action
      </button>

      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
            <th></th>
          </tr>
        </thead>

        <tbody>
          {state.rows.map(({ id, name }, index) => (
            <tr key={id}>
              <td>{id}</td>
              <td>{name}</td>
              <td>
                <button onClick={() => dispatch({ type: "remove", id, index })}>
                  Delete
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </>
  );
}
```

As mentioned in the comments, `history` is our stack. When a user clicks one of the “Remove” buttons we add the action plus some additional data to the history. The “Undo Last Action” button then removes the latest action from the `history` stack and adds the row again to the data.

[![React on the job - email course](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/skyncbvxln80rmks31wq.png)](https://profy.dev/around-react-email-workshop)

## Queue

### Description

A queue is very similar to a stack. But instead of removing the element that was added last, we remove the element that was first added to the queue. This is called “First in, first out” (aka FIFO).

Like a queue in the supermarket. 

We can implement it again using a simple array as it's not natively supported in JavaScript.

```typescript
const queueu = [];

// add item to the "end"
queueu.push("value");

// remove item from the "beginning"
const firstItem = queueu.shift();
```

### Real-World Example: Notifications

In the wild, you can find a queue when you have show notifications on top of each other. You know, the kind of notification that pops up at the bottom of the screen and disappears after a while. 

In this case, a notification pops up whenever a user joins our imaginary community.

![Example of notification queue](no-optimization::https://media.graphassets.com/qINezXxwSPqGDLqZdGAS)

Whenever we click the button a message is added to the `notifications` array (our queue). Additionally, a timeout is set that removes the first notification in the queue again. You can find the complete code [here on CodeSandbox](https://codesandbox.io/s/javascript-data-structures-78k6fe?file=/src/QueueExample.jsx).

```typescript
import { faker } from "@faker-js/faker";
import { useState } from "react";

function ButtonAddingNotifications() {
  const [notifications, setNotifications] = useState([]);

  const addNotification = () => {
    setNotifications((previous) => {
      // use Array.concat() as immutable alternative to Array.push()
      return previous.concat(`${faker.name.firstName()} joined!`);
    });
    setTimeout(() => {
      // use Array.slice() as immutable alternative to Array.shift()
      setNotifications((previous) => previous.slice(1));
    }, 1000);
  };

  return (
    <>
      <button onClick={() => addNotification()}>
        Invite User To Community
      </button>

      <aside>
        {notifications.map((message, index) => (
          <p key={index}>{message}</p>
        ))}
      </aside>
    </>
  );
}
```

## Tree

### Description

A tree is a nested data structure. It starts with a parent that has children. The children again can have children of their own and so on. Together with trees you often find recursive functions.

Here is one example using a nested object:

```typescript
{
  name: "Parent",
  children: [
    {
      name: "Child 1",
    },
    {
      name: "Child 2",
      children: [
        {
          name: "Grandchild 21",
        }
      ]
    }
  ]
}
```

There are many different ways of defining a tree. As an alternative to the above structure, we could also use a flat array. In that case, all elements need an ID and reference each other.

```typescript
[
  {
    id: "parent-1",
    name: "Parent",
    // references to children via their IDs
    children: ["child-1", "child-2"]
  },
  {
    id: "child-1",
    // reference to the parent vid its ID
    name: "Child 1",
    parent: "parent-1"
  },
  {
    id: "child-2",
    name: "Child 2",
    parent: "parent-1",
    children: ["grandchild-2"]
  },
  {
    id: "grandchild-21",
    name: "Grandchild 21",
    parent: "child-2"
  }
]
```

The weird thing from a human perspective is that each child can only have one parent. That’s why you can also often find the terms “node” (aka any of the items), “child”, and “edge” (the connection between two items).

### Real-World Example: Nested menus or comments.

As mentioned, trees are nested data structures. And you can find those when you have nested menus that are defined on the backend (e.g. in a CMS).

![Nested menu tree](https://media.graphassets.com/8vSFbK4JTuyEvLEePLgx)

Another common example is nested comments as you can find them on many blogs or Reddit. This component recursively renders a nested tree structure consisting ([the complete code on CodeSandbox](https://codesandbox.io/s/javascript-data-structures-78k6fe?file=/src/TreeExample.jsx)).

```typescript
const menuItems = [
  {
    text: "Menu 1",
    children: [
      {
        text: "Menu 1 1",
        href: "#11",
      },
      {
        text: "Menu 1 2",
        href: "#12",
      },
    ],
  },
  {
    text: "Menu 2",
    href: "#2",
  },
  {
    text: "Menu 3",
    children: [
      {
        text: "Menu 3 1",
        children: [
          {
            id: "311",
            text: "Menu 3 1 1",
            href: "#311",
          },
        ],
      },
    ],
  },
];

// Menu and MenuItem are recursively calling each other
function Menu({ items }) {
  return (
    <ul>
      {items.map((item, index) => (
        <MenuItem key={index} {...item} />
      ))}
    </ul>
  );
}

function MenuItem({ text, href, children }) {
  // the break condition: 
  // stop recursion if the item doesn't have children
  if (!children) {
    return (
      <li>
        <a href={href}>{text}</a>
      </li>
    );
  }

  // recursively create a submenu
  return (
    <li>
      {text}
      <Menu items={children} />
    </li>
  );
}

// the root component
function NestedMenu() {
  return <Menu items={menuItems} />;
}
```

The nested tree structure above is quite easy to read for humans and also simple to render. But if we need to update the data we quickly run into problems. Especially when we need to change the data immutably (as we have to with React state).

In this case, a great alternative is a flat array instead of the nested objects above. This is a bit heavier on the frontend logic as we have to resolve the ID references. But it’s very easy to interact with and probably also easier to handle for the backend. 

```typescript
const menuItems = [
  {
    id: "1",
    text: "Menu 1",
    children: ["11", "12"],
    isRoot: true,
  },
  {
    id: "11",
    text: "Menu 1 1",
    href: "#11",
  },
  {
    id: "12",
    text: "Menu 1 2",
    href: "#12",
  },
  {
    id: "2",
    text: "Menu 2",
    href: "#2",
    isRoot: true,
  },
  {
    id: "3",
    text: "Menu 3",
    children: ["31"],
    isRoot: true,
  },
  {
    id: "31",
    text: "Menu 3 1",
    children: ["311"],
  },
  {
    id: "311",
    text: "Menu 3 1 1",
    href: "#311",
  },
];

function Menu({ itemIds, itemsById }) {
  return (
    <ul>
      {itemIds.map((id) => (
        <MenuItem key={id} itemId={id} itemsById={itemsById} />
      ))}
    </ul>
  );
}

function MenuItem({ itemId, itemsById }) {
  const item = itemsById[itemId];
  if (!item.children) {
    return (
      <li>
        <a href={item.href}>{item.text}</a>
      </li>
    );
  }
  return (
    <li>
      {item.text}
      <Menu itemIds={item.children} itemsById={itemsById} />
    </li>
  );
}

function NestedMenu() {
  const itemsById = menuItems.reduce(
    (prev, item) => ({ ...prev, [item.id]: item }),
    {}
  );
  const rootIds = menuItems.filter(({ isRoot }) => isRoot).map(({ id }) => id);
  return <Menu itemIds={rootIds} itemsById={itemsById} />;
}
```

Note that recursive functions are very elegant but for very deeply nested trees you might exceed the JavaScript engine’s call stack size. Here is an example that throws an error at a recursion depth of 10962:

![Example of a recursive function exceeding the maximum call stack size](https://media.graphassets.com/MVS7W1GuTeSkxmVvfDke)

In our case, we won’t run into this problem as we won’t nest thousands of menu items.

[![React on the job - email course](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/skyncbvxln80rmks31wq.png)](https://profy.dev/around-react-email-workshop)