Normalized Stores: A Database-Inspired Approach to Frontend State Management

This article delves into the concept of normalized stores in frontend architecture, a technique that mimics database design for predictable, scalable state in large applications. We will explore how Redux, RTK Query, and enterprise applications structure their data to enhance maintainability.

ShareShare

Normalized Stores: A Database-Inspired Approach to Frontend State Management

In the early stages of application development, frontend state management appears relatively straightforward. You may have a user object, a counter, or a few lists to manage. However, as your application grows in complexity, dealing with paginated results, syncing updates across views, and handling optimistic changes, your state can easily devolve into a chaotic mix of duplicated objects, stale data, and intricate mutation sequences.

This is where normalized stores enter the picture.

Drawing inspiration from relational database design principles, normalized stores have been advocated by tools such as Redux Toolkit, RTK Query, NgRx Entity, and Apollo Client. By structuring your client-side state as if it were a database, you enable reliable querying, effortless cache updates, and a scalable architecture.


The Rationale Behind State Normalization

Consider a social media application scenario. You fetch a user's profile, and later, fetch their posts. This user now exists in both API responses, one as an in-depth object, and the other as a nested field inside posts.

What if you now need to update their name?

You are presented with three options:

  • Manually mutate all references?
  • Refetch everything?
  • Risk having stale or out-of-sync state?

This dilemma illustrates the central issue. When state is denormalized, i.e., nested, repeated, and tightly coupled, it becomes increasingly difficult to update, track, and scale.

Normalization comes in as a solution, ensuring that:

  • Each entity exists in one place only.
  • Relationships are stored by ID reference.
  • Updates are localized and composable.

These principles align closely with those of a relational database.


Visualizing Normalized State

Suppose you are dealing with users and posts.

Denormalized example:

{
  "posts": [
    {
      "id": 1,
      "author": { "id": 42, "name": "Jane" },
      "content": "Hello world"
    }
  ]
}

In this example, if name changes, you need to update it everywhere it appears.

Normalized version:

{
  "entities": {
    "users": {
      "42": { "id": 42, "name": "Jane" }
    },
    "posts": {
      "1": { "id": 1, "authorId": 42, "content": "Hello world" }
    }
  },
  "result": [1]
}

In the normalized version, users[42] becomes the single source of truth. All components that use this data will react to a single update, eliminating duplication and stale data.


Tools That Facilitate Normalization

Redux Toolkit (RTK)

Redux Toolkit's createEntityAdapter() provides out-of-the-box support for normalization:

const usersAdapter = createEntityAdapter<User>();

const usersSlice = createSlice({
  name: 'users',
  initialState: usersAdapter.getInitialState(),
  reducers: {
    addUser: usersAdapter.addOne,
    updateUser: usersAdapter.updateOne,
  },
});

RTK Query also automatically normalizes responses by entity type, allowing for efficient cache updates and background re-fetching.

NgRx Entity

In the Angular ecosystem, NgRx Entity offers similar adapters, selectors, and update patterns, assisting developers in managing large datasets across routes and views.

Apollo Client

Apollo Client stores entities by ID in its normalized in-memory cache, automatically resolving relationships and updating dependent queries when fields change.


Real-World Examples

Slack

Slack utilizes normalization for messages, users, and channels to ensure that UI updates (such as a new message or status change) reflect across all panels without the need for data duplication. The same user appears in message threads, member lists, and direct messages, all referencing the same store entry.

Trello / Jira

Project management tools like Trello normalize cards, lists, and users to manage complex drag-and-drop workflows without rebinding entire trees of nested state. Moving a card only updates one entry, not the entire list.

GitHub (Issues, Comments, Repos)

With a normalized cache, it's easier to update a single comment or label across issue pages, notifications, and dashboards, keeping the UI consistent without triggering massive re-renders.


Advantages of Normalization

  • ๐Ÿง  Unambiguous and consistent data structure
  • ๐Ÿ” Simplified updates and merging of new data
  • ๐Ÿ” Efficient caching and reactivity
  • ๐Ÿงฉ Reusable selectors
  • โš–๏ธ Supports pagination, optimistic updates, and background syncing
  • ๐Ÿงผ Prevents stale, orphaned, or duplicated state

Challenges and Considerations

  • Requires more upfront setup: adapters, ID mapping, relational logic
  • Requires consistent ID usage in API responses
  • Querying relationships can be verbose without selectors
  • Debugging can be more challenging with deeply nested references
  • Custom UI requires careful selector design to prevent over-fetching

Nevertheless, for applications with complex relationships, or any application that accesses the same data from multiple views, normalization is a crucial design pattern.


Best Practices

  • Use createEntityAdapter() or normalizr to abstract data transformations
  • Keep entities and ids separately in state slices
  • Write selectors that resolve relationships, e.g., selectPostWithAuthor(postId)
  • Normalize at the API layer if possible (e.g., using RTK Query's transformResponse)
  • Avoid over-normalization, primitive one-off values can remain flat

Conclusion: Design Like a Database

Frontend state is not just a temporary object store. As applications scale, state transforms into the frontend's internal database. It demands the same level of structure, discipline, and care that you would apply to a backend schema.

Normalized stores provide this structure. They empower applications to scale gracefully, minimize duplication, and maintain predictability, even when data flows become complex.

So, build your state like you'd build a database. At scale, that's precisely what it becomes.

Get the series as it ships

New parts of Agent Access Control, plus analysis of agent identity standards as they move. Roughly monthly. No filler, no cadence promises I won't keep.

about

Ehsan Hosseini

Ehsan Hosseini

me [at] ehosseini [dot] info

I work on what AI agents are allowed to do inside companies, and whether you can prove who authorized it. I write about it here, and build it at my company.

How it works?

I write what I learn while building systems that decide what an AI agent is allowed to do. The thinking, the arguments, and the calls about what's true are mine. I use AI to draft, structure, and speed up research, the same way I'd use any other tool. It doesn't decide what I believe.

I check technical claims against primary sources before a post goes out: the specs, the RFCs, the original documentation, not a model's memory of them. When I'm uncertain, I say so. When something is opinion rather than fact, I mark it as one. If I get something wrong, that's on me.

ยฉ 2026 Ehsan Hosseini. All rights reserved.