# How to Structure Large Applications with Multiple Zustand Stores Using the Slices Pattern

> Structure large React applications with multiple Zustand stores using the flexible slices pattern. Split monolithic stores into composable slices for better organization and maintainability.

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

---

**Use the slices pattern to split a monolithic Zustand store into small, composable slice creators that are merged into a single bounded store with shared middleware and full type inference.**

As applications grow, a single monolithic store in `pmndrs/zustand` becomes difficult to maintain—state files swell, TypeScript inference slows, and developers lose track of which features own specific state branches. The **slices pattern** solves this by treating each feature as an isolated slice creator function that receives the store's `set` and `get` functions, then combining them into one bounded store. This approach is officially documented in [`docs/learn/guides/slices-pattern.md`](https://github.com/pmndrs/zustand/blob/main/docs/learn/guides/slices-pattern.md) and recommended in the README under *Best practices* for maintainable architecture.

## Why Monolithic Stores Fail at Scale

A single `create()` call containing every piece of global state eventually creates tight coupling between unrelated features. When multiple developers modify the same massive state object, merge conflicts increase and refactoring becomes risky.

The slices pattern provides four key advantages for large codebases:

- **Modularity** – Each feature lives in its own file (e.g., [`src/stores/fishSlice.ts`](https://github.com/pmndrs/zustand/blob/main/src/stores/fishSlice.ts)), making it easy to locate, test, and refactor without affecting unrelated code.
- **Type Safety** – Slice creators expose strictly typed state and actions that are merged with inferred types via the `combine` middleware.
- **Middleware Consistency** – Apply `persist`, `devtools`, or custom middleware once at the root level in [`useBoundStore.ts`](https://github.com/pmndrs/zustand/blob/main/useBoundStore.ts) rather than duplicating logic across slices, which prevents subtle bugs from conflicting middleware instances.
- **Coordinated Updates** – Actions inside one slice can call actions from another slice through the shared `get()` function, enabling cross-feature workflows without direct imports or tight coupling.

## Core Concepts of the Zustand Slices Pattern

Understanding three fundamental building blocks is essential before implementing this architecture.

### Slice Creators

A slice creator is a function with the signature `(set, get) => { ...state, ...actions }`. Unlike standard Zustand stores, it does not call `create()` itself; instead, it returns a plain object containing state properties and action methods. Because it receives the same `set` and `get` functions that the root store receives, slice actions can read or update any part of the combined state, including state owned by other slices.

### The combine Middleware

Located at [`src/middleware/combine.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/combine.ts), the `combine` middleware merges an initial static state object with a slice creator function. Internally, it uses `Object.assign` to produce a new `StateCreator` that yields the final store shape. This utility allows TypeScript to infer the complete state type from the individual slice return types without manual interface declarations.

### Bounded Stores

The **bounded store** is the final Zustand hook created by calling `create()` (or `createStore` for vanilla JS) with all slices spread into the root state object. This single hook exposes the union of every slice's state and actions while maintaining a single source of truth for middleware configuration.

## Architectural Layout for Large Applications

Organize your source code so that each slice owns its domain logic, and the store composition happens in a dedicated entry point:

```

src/
│
├─ stores/
│   ├─ fishSlice.ts      // createFishSlice
│   ├─ bearSlice.ts      // createBearSlice
│   ├─ bearFishSlice.ts  // optional cross-slice actions
│   └─ useBoundStore.ts  // combine slices & apply middlewares
│
└─ components/
    └─ App.tsx           // consumes useBoundStore

```

This structure ensures that the [`useBoundStore.ts`](https://github.com/pmndrs/zustand/blob/main/useBoundStore.ts) file remains the single location where middlewares like `persist` or `devtools` are attached. When business logic changes, developers modify only the relevant slice file rather than hunting through a thousand-line store definition.

## Implementing the Slices Pattern Step by Step

Follow this sequence to refactor an existing monolithic store or start a new project with proper separation of concerns.

### Creating Individual Slices

Define each feature's state and actions in isolation. The slice creator receives `set` for updates and `get` for reading current state.

```typescript
// src/stores/fishSlice.ts
export const createFishSlice = (set, get) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})

```

```typescript
// src/stores/bearSlice.ts
export const createBearSlice = (set, get) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
  // Cross-slice action that modifies another slice's state
  eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})

```

### Handling Cross-Slice Coordination

When an action needs to orchestrate multiple slices without tight coupling, create a dedicated coordination slice that calls other actions via `get()`.

```typescript
// src/stores/bearFishSlice.ts
export const createBearFishSlice = (set, get) => ({
  addBearAndFish: () => {
    get().addBear()
    get().addFish()
  },
})

```

### Combining Slices and Applying Middleware

In [`useBoundStore.ts`](https://github.com/pmndrs/zustand/blob/main/useBoundStore.ts), spread all slice creators into a single object and wrap them with middleware. According to the official documentation in [`docs/learn/guides/slices-pattern.md`](https://github.com/pmndrs/zustand/blob/main/docs/learn/guides/slices-pattern.md), middlewares must only be applied at this top level to avoid duplicated listeners and unexpected state resets.

```typescript
// src/stores/useBoundStore.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { createBearSlice } from './bearSlice'
import { createFishSlice } from './fishSlice'
import { createBearFishSlice } from './bearFishSlice'

export const useBoundStore = create(
  persist(
    (set, get) => ({
      ...createBearSlice(set, get),
      ...createFishSlice(set, get),
      ...createBearFishSlice(set, get),
    }),
    { name: 'my-app-store' }
  )
)

```

### Consuming the Bounded Store

Components import the single `useBoundStore` hook and select specific state slices or actions.

```tsx
// src/components/App.tsx
import { useBoundStore } from '../stores/useBoundStore'

export default function App() {
  const bears = useBoundStore((state) => state.bears)
  const fishes = useBoundStore((state) => state.fishes)
  const addBear = useBoundStore((state) => state.addBear)
  const addBoth = useBoundStore((state) => state.addBearAndFish)

  return (
    <div>
      <h2>Bears: {bears}</h2>
      <h2>Fishes: {fishes}</h2>
      <button onClick={addBear}>Add Bear</button>
      <button onClick={addBoth}>Add Both</button>
    </div>
  )
}

```

## Best Practices for Production Applications

Maintain code quality and performance in large applications by adhering to these guidelines derived from the `pmndrs/zustand` source code and documentation.

**Attach Middlewares Exclusively in the Combined Store**

Never apply `persist`, `devtools`, or logging middleware inside individual slice files. Applying middleware at the slice level creates multiple subscriber layers and can cause state synchronization issues. The `combine` implementation in [`src/middleware/combine.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/combine.ts) expects middleware to wrap the final merged creator.

**Keep Slice Creators Pure**

Slice creators should return plain JavaScript objects. Side effects such as API calls or localStorage access belong inside action methods, not in the slice creator's outer scope. This purity enables unit testing by allowing you to instantiate temporary stores with `createStore` without mounting the entire application.

**Leverage TypeScript Inference**

Declare explicit return types on your slice creators, then let the `combine` middleware infer the full store shape. This provides autocomplete for state keys and compile-time validation that cross-slice actions exist on the `get()` object.

**Test Slices in Isolation**

Because each slice is a pure function of `(set, get)`, you can import a slice creator into a test file, create a temporary store with `createStore`, and verify action logic without rendering React components or importing the full bounded store.

## Summary

- The **slices pattern** splits large Zustand stores into feature-focused slice creator functions that are merged into one bounded store.
- Use the **`combine` middleware** from [`src/middleware/combine.ts`](https://github.com/pmndrs/zustand/blob/main/src/middleware/combine.ts) to merge initial state with slice creators while preserving type inference.
- Apply middlewares **only once** in the root [`useBoundStore.ts`](https://github.com/pmndrs/zustand/blob/main/useBoundStore.ts) file to avoid duplicated listeners and state inconsistency.
- Enable **cross-slice communication** by calling `get().otherSliceAction()` inside action methods, allowing coordinated updates without tight coupling.
- Reference [`docs/learn/guides/slices-pattern.md`](https://github.com/pmndrs/zustand/blob/main/docs/learn/guides/slices-pattern.md) and the *Best practices* section of [`README.md`](https://github.com/pmndrs/zustand/blob/main/README.md) for official architectural guidance from the `pmndrs/zustand` maintainers.

## Frequently Asked Questions

### What is the Zustand slices pattern?

The Zustand slices pattern is an architectural approach where you define small, reusable **slice creator functions**—each responsible for a specific feature's state and actions—and then merge them into a single store using the `combine` middleware or object spreading. This pattern is officially documented in [`docs/learn/guides/slices-pattern.md`](https://github.com/pmndrs/zustand/blob/main/docs/learn/guides/slices-pattern.md) and designed to prevent monolithic state files in large applications.

### How do I share state between different slices?

Actions within one slice can access the entire store's state and actions through the **`get()`** function passed to the slice creator. For example, an action in [`bearSlice.ts`](https://github.com/pmndrs/zustand/blob/main/bearSlice.ts) can call `get().addFish()` to trigger an update in [`fishSlice.ts`](https://github.com/pmndrs/zustand/blob/main/fishSlice.ts), or directly modify `fishes` in the `set()` call. This enables cross-slice updates without importing slice files into each other.

### Where should I apply middleware in a sliced store?

Always apply middleware—such as `persist`, `devtools`, or `immer`—in the **root store file** (e.g., [`useBoundStore.ts`](https://github.com/pmndrs/zustand/blob/main/useBoundStore.ts)) that combines all slices. According to the Zustand documentation, applying middleware inside individual slice files causes duplicated middleware instances and unexpected behavior. The combined store should be the single point where middleware wraps the final merged state creator.

### Can I use the slices pattern with TypeScript?

Yes, the slices pattern is fully compatible with TypeScript and provides excellent type safety. Define return types for your slice creators (e.g., `BearSlice`), then use `combine` or explicit type annotations on the bounded store to let TypeScript infer the union of all slice types. The guide in [`docs/learn/guides/advanced-typescript.md`](https://github.com/pmndrs/zustand/blob/main/docs/learn/guides/advanced-typescript.md) details how to type slices so that `get()` correctly recognizes actions from other slices at compile time.