Back to home

Filed Jul 3, 2026

The "40KB Problem": Memory, Pointers, and Immutability from C to React

Have you ever paused mid-keystroke to wonder what actually happens to your computer's RAM when you update a piece of state in a React component?

If you started your coding journey with a low-level language like C, you were probably trained to micro-manage every single byte. If you grew up in the modern web ecosystem, you were likely taught to prioritize data integrity, pure functions, and the golden rule of immutability.


But underneath the hood, these two philosophies clash in a really interesting way. Let’s look at a subtle paradox: How our standard habits for keeping data safe in JavaScript can quietly slow down our apps, and how modern web tools solve it by stealing a page from low-level systems programming.

1. The C Perspective: The Cost of a Copy

In C, the default behavior for functions is pass-by-value. If you send an integer over to a function, the computer makes a literal duplicate of that integer. It’s simple, isolated, and incredibly cheap.


But things get tricky when your data structures grow. Imagine a custom profile object holding a player’s statistics and inventory that consumes about 40KB of memory:

typedef struct {
    int id;
    int inventory[10000]; // ~40KB of data
} PlayerData;

// When we call this, the system duplicates the 40KB into the function,
// and then copies 40KB back out to save it.
PlayerData updateScore(PlayerData p) {
    p.inventory[0] += 10; 
    return p; 
}

In systems programming, doing this inside a tight loop is a massive red flag. You’re forcing the CPU to move a large number of integers back and forth just to tweak a single number. To fix this, a C developer swaps out the raw value for a pointer (PlayerData *p). Instead of moving the whole mountain of data, you pass an 8-byte memory address, change the number directly at the source, and move on.

// EFFICIENT: We pass an 8-byte pointer (address) instead of the whole 40KB
void updateScore(PlayerData *p) {
    // The '->' operator dereferences the pointer to access the struct's fields
    p->inventory[0] += 10; 
    
    // No return needed! We modified the original memory directly.
}

The Catch: Pointers are fast, but they trade away safety. Any function with that address can accidentally overwrite your master data without you realizing it.

2. The JavaScript Twist: The “Shared Address” Trap

Frontend developers look at direct pointer mutation and see a recipe for hard-to-track bugs. In a large web application, if fifty different UI components share a single object, and one of them silently mutates a nested value, your UI can easily fall out of sync.


But wait—isn’t JavaScript pass-by-value too? Yes, but it depends on what you’re passing.


When dealing with JavaScript objects and arrays, the engine doesn’t copy the actual data structure; it copies the reference (the 8-byte address pointing to where that object lives in memory).

function attemptChange(player) {
    player.score = 99; // Heads up: This changes the original object outside!
}

Because JavaScript passes the pointer by value, passing a massive object into a function costs next to nothing. It’s lightning-fast! But it completely bypasses automatic immutability. You get excellent performance, but you lose the safety net of isolated data.

3. The React Tax: Manual Immutability

To prevent unexpected side effects, React requires us to treat state as read-only. If you want to change a value, you have to hand React a completely new object reference so it knows it needs to re-render the screen.


Because of this, we often rely on JavaScript’s most popular tool: The Spread Operator (...).

// The typical Vanilla React approach
const [player, setPlayer] = useState({ id: 1, inventory: [40KB array], score: 10 });

setPlayer(prev => ({
  ...prev,
  score: prev.score + 1
}));

Here is the fine print that is easy to overlook: The spread operator only performs a shallow copy.

  • If your object is flat, you just duplicated that whole 40KB array into a fresh slot in RAM. You’ve accidentally recreated the exact memory copying issue we ran away from in C!

  • If your object is deeply nested, the spread operator only copies the top layer. The inner layers still point to the original memory locations, meaning you haven’t actually protected your nested data from accidental mutations.

4. The Clever Middle Ground: Structural Sharing

So, how do we get the data isolation of a full copy without the performance penalty of duplicating massive structures?


We use a strategy called Structural Sharing. Instead of cloning the entire 40KB object every time a value changes, we treat our data like a tree of branches. When a single value changes, we copy only the specific path of branches leading directly to that change.


The remaining 99% of the tree stays right where it is. Both your old state pointer and your new state pointer safely share the exact same unchanged memory branches.

[Old Root Node]          [New Root Node]
     /         \               /         \
[Unchanged]  [Target]     [Unchanged]  [Copied Path]
                  \                         \
                (Old Leaf)                (New Leaf)

The good news is that you don’t have to build these trees by hand. Modern state utilities like Redux Toolkit, Zustand (paired with Immer), and data-fetching tools like TanStack Query handle this automatically.

When you use a tool like Immer, you get to write clean code that reads like low-level pointer mutation, while the library manages the high-efficiency tree splitting behind the scenes:

// Looks like direct mutation, but executes with smart structural sharing
updatePlayer(draft => {
  draft.stats.hp += 10; // Only the 'stats' branch gets a new memory address!
});

Summary: The Memory Cheat Sheet

ApproachUnder the HoodRAM FootprintSafety Level
C (Pass-by-Value)Full Data CopyHigh (Deep Copy)High
C (Pointers)Passes a direct addressMinimal (8-byte Address)Low
JS (useState + Spread)Clones top-level propertiesMedium (Shallow Copy)Partial
JS (Immer / TanStack)Structural SharingLow (Only copies changes)Total