# How to Use Custom Equality Functions with createWithEqualityFn in Zustand

> Learn to use createWithEqualityFn in Zustand to prevent unnecessary re-renders by implementing custom equality functions for state slices. Optimize your React app's performance today.

- Repository: [Poimandres/zustand](https://github.com/pmndrs/zustand)
- Tags: how-to-guide
- Published: 2026-03-06

---

**Use `createWithEqualityFn` from `zustand/traditional` to build a store that accepts either a default equality function during initialization or per-call equality functions via the hook's second argument, preventing unnecessary React re-renders when state slices are structurally equal but referentially different.**

The `pmndrs/zustand` repository provides a specialized factory called `createWithEqualityFn` for developers who need fine-grained control over component re-rendering behavior. Unlike the standard `create` API, this utility allows you to supply custom equality functions—such as shallow or deep comparators—either globally for the entire store or individually for specific selectors.

## What is createWithEqualityFn?

`createWithEqualityFn` is a factory function exported from `zustand/traditional` that constructs a **bound store hook** with built-in support for equality comparisons. According to the source code in [[`src/traditional.ts`](https://github.com/pmndrs/zustand/blob/main/src/traditional.ts)](https://github.com/pmndrs/zustand/blob/main/src/traditional.ts), the factory:

1. Creates the underlying store using `createStore` from [[`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts)](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts)
2. Returns a hook that wraps `useSyncExternalStoreWithSelector` to handle selector and equality logic
3. Accepts an optional `defaultEqualityFn` parameter that applies to all selector calls unless overridden

The returned hook can be invoked in three ways:
- Without arguments → returns the entire state object
- With a selector → returns the selected slice using the default equality function
- With a selector and equality function → uses the provided equality function for that specific call

## Installation and Basic Setup

To use `createWithEqualityFn`, import it from the traditional submodule rather than the main Zustand package.

```typescript
import { createWithEqualityFn } from 'zustand/traditional'
import { shallow } from 'zustand/shallow'

// Create store with optional default equality function
const useStore = createWithEqualityFn(
  (set) => ({
    items: [] as string[],
    count: 0,
    addItem: (item: string) => set((state) => ({ 
      items: [...state.items, item],
      count: state.count + 1
    }))
  }),
  shallow // Optional: sets default equality for all selectors
)

```

## Using Per-Call Custom Equality Functions

The most common pattern involves passing an equality function as the second argument to the hook when selecting specific state slices. This approach gives you granular control over which components re-render.

### Basic Selector with Shallow Equality

Use the `shallow` helper from `zustand/shallow` when selecting arrays or objects that may have new references but identical contents.

```typescript
import { shallow } from 'zustand/shallow'

function ItemList() {
  // Only re-renders if items array changes shallowly
  const items = useStore(state => state.items, shallow)
  
  return (
    <ul>
      {items.map(item => <li key={item}>{item}</li>)}
    </ul>
  )
}

```

### Deep Equality for Complex Objects

For nested objects where you need to compare values at arbitrary depths, use a deep equality function like `fast-deep-equal`.

```typescript
import deepEqual from 'fast-deep-equal'

function UserProfile() {
  // Re-renders only when user object is deeply different
  const user = useStore(
    state => state.user,
    deepEqual
  )
  
  return <div>{user.name} - {user.age}</div>
}

```

## Setting a Default Equality Function

When you want consistent equality behavior across your entire application without repeating the equality function in every component, pass it as the second argument to `createWithEqualityFn`.

### Global Deep Equality Configuration

```typescript
import { createWithEqualityFn } from 'zustand/traditional'
import deepEqual from 'fast-deep-equal'

const useStore = createWithEqualityFn(
  (set) => ({
    config: { theme: 'light', lang: 'en' },
    updateConfig: (newConfig) => set({ config: newConfig })
  }),
  deepEqual // All selectors use deep equality by default
)

// Component uses default deep equality automatically
function ConfigDisplay() {
  const config = useStore(state => state.config)
  return <div>{config.theme}</div>
}

```

### Overriding Defaults for Specific Selectors

Even with a default equality function, you can override it for specific use cases by passing a different function to the hook.

```typescript
import { shallow } from 'zustand/shallow'

function SpecificComponent() {
  // Override global deepEqual with shallow for this selector
  const items = useStore(state => state.items, shallow)
  return <div>{items.length} items</div>
}

```

## How It Works Under the Hood

The implementation in [[`src/traditional.ts`](https://github.com/pmndrs/zustand/blob/main/src/traditional.ts)](https://github.com/pmndrs/zustand/blob/main/src/traditional.ts) reveals the architectural details:

1. **Store Creation**: The factory calls `createStore` from [[`src/vanilla.ts`](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts)](https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts) to create the underlying store API (`getState`, `setState`, `subscribe`).

2. **Hook Binding**: The returned hook (`useBoundStoreWithEqualityFn`) wraps `useStoreWithEqualityFn`, which internally uses `useSyncExternalStoreWithSelector` from the `use-sync-external-store` shim (lines 66-80 of [`src/traditional.ts`](https://github.com/pmndrs/zustand/blob/main/src/traditional.ts)).

3. **Equality Resolution**: The hook resolves equality functions in this priority:
   - Per-call equality function (highest priority)
   - `defaultEqualityFn` passed to `createWithEqualityFn`
   - Strict reference equality (`===`) when neither is provided

4. **TypeScript Support**: The file defines three overloads for `useStoreWithEqualityFn` to ensure type safety whether you call the hook with no arguments, with a selector only, or with both selector and equality function.

## Summary

- **`createWithEqualityFn`** from `zustand/traditional` creates stores that support custom equality comparisons beyond strict reference equality.
- You can supply a **default equality function** during store creation that applies to all selectors, or pass equality functions **per-call** for granular control.
- The implementation relies on `useSyncExternalStoreWithSelector` in [[`src/traditional.ts`](https://github.com/pmndrs/zustand/blob/main/src/traditional.ts)](https://github.com/pmndrs/zustand/blob/main/src/traditional.ts) to efficiently compare selected state slices and prevent unnecessary React re-renders.
- Common equality strategies include `shallow` from `zustand/shallow` for arrays/objects and `fast-deep-equal` for nested structures.

## Frequently Asked Questions

### What is the difference between `create` and `createWithEqualityFn`?

The standard `create` API from `zustand` uses strict reference equality (`===`) to determine when selected state slices change, which can cause unnecessary re-renders when you return new object or array references that contain the same data. `createWithEqualityFn`, exported from `zustand/traditional`, allows you to supply custom equality functions—such as shallow or deep comparators—either as a store-wide default or per selector call, giving you precise control over re-render behavior.

### When should I use shallow vs deep equality?

Use **shallow equality** (via `zustand/shallow`) when selecting arrays or objects where you want to compare only the first level of properties—this is ideal for lists where item order matters but nested object contents don't require comparison. Use **deep equality** (via libraries like `fast-deep-equal`) when selecting nested objects or trees where changes at any depth should trigger re-renders, though be aware that deep comparisons carry higher computational costs and should be used sparingly on large state trees.

### Can I use `createWithEqualityFn` without TypeScript?

Yes, `createWithEqualityFn` works identically in JavaScript projects. Import it from `zustand/traditional` and pass your state creator function and optional equality function as arguments. The API surface is the same: the returned hook accepts an optional selector function and an optional equality function as its second argument.

### How do I debug re-render issues with custom equality functions?

Start by verifying that your equality function actually returns `true` when you expect it to—log the previous and next values inside a custom equality function to ensure they're being compared correctly. Check that you're importing `createWithEqualityFn` from `zustand/traditional` rather than the standard `create` from `zustand`, as the latter doesn't support the equality function parameter. Finally, use React DevTools Profiler to confirm that components using the store hook are indeed skipping re-renders when your equality function returns true.