PAUL CHONGSenior Software Engineer

Data Normalization in the Frontend

2026-08-04 · 3 min read

Data Normalization is the process of organizing data to reduce redundancy and improve data integrity. In the frontend, normalization typically involves transforming nested or hierarchical data into a flat structure, where each entity is represented by a unique identifier. This allows for easier updates, deletions, and retrieval of data, as well as improved performance and maintainability.

Why Normalize Data?

  1. Reduce Redundancy: normalization helps eliminate duplicate data, which can lead to inconsistencies and increased storage requirements. Each entity exists once in memory and is referenced by its unique identifier.
  2. Improve Data Integrity: when data is stored in a single location, updates and deletions can be performed more efficiently, reducing the risk of errors.
  3. Enhance Performance: changing an entity updates it everywhere it is referenced.
  4. Optimized Re-renders: shallow equality checks become more efficient, enabling memoization and preventing unnecessary re-renders in UI frameworks like React.
  5. Predictable State Changes: immutable updates to normalized state are more predictable and debuggable.

Implementation Strategies

  1. Flat Entities

Store each entity type in a flat lookup "table" keyed by ID. This allows for easy access and manipulation of individual entities without the need to traverse nested structures.

// Before: Nested data structure, O(n) access
const users = [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}];

// After: Flat lookup table, O(1) access
const users = {
  1: {id: 1, name: 'Alice'},
  2: {id: 2, name: 'Bob'}
};
  1. Use Unique Identifiers

Assign unique identifiers to each entity. This allows for easy referencing and manipulation of entities, as well as efficient updates and deletions.

// Before: Post with nested author object
const post = {id: 1, author: {id: 101, name: 'Alice'}, content: 'Hello World'};

// After: Post with normalized author reference
const post = {id: 1, authorId: 101, content: 'Hello World'};
  1. Separate Entity Stores

Maintain separate stores for each entity type. This allows for better organization and separation of concerns, making it easier to manage and update individual entities.

// Separate stores for users and posts
const state = {
  entities: {
    users: { /*user entities */},
    posts: { /*post entities */}
  }
};
  1. Computed vs Stored Data

Store only the essential data and compute derived data on-the-fly. This reduces redundancy and ensures that derived data is always up-to-date.

// Before: Storing totalPrice in the cart object
const cart = {
  items: [{productId: 1, quantity: 2}, {productId: 2, quantity: 1}],
  totalPrice: computeTotalPrice(cart.items)
};

// After: Instead of storing totalPrice, compute it when needed
const cart = { items: [...] };
const totalPrice = computeTotalPrice(cart.items);
  1. Relationship Management

Create separate relationship mappings for many-to-many and one-to-many relationships. This allows for efficient querying and manipulation of related entities without duplicating data.

const state = {
  users: { /* user entities */ },
  groups: { /* group entities */ },
  userGroups: {
    byUser: { 1: [10, 20], 2: [10] },   // userId -> groupIds
    byGroup: { 10: [1, 2], 20: [1] }    // groupId -> userIds
  }
};

When to Normalize vs Denormalize?

Normalize when updates and consistency matter. Denormalize when reads and convenience matter.

Normalize when...

  • The entity appears in many places
  • The entity is updated frequently
  • You need cache consistency
  • You need to update/delete individual entities efficiently
  • Relationships are complex
  • You have client-side store/cache such as Relay, Redux, etc...

Denormalize when...

  • You're primarily reading data
  • Data is relatively systematic
  • You don't need to independently update/delete entities
  • The cost of duplicated data is low