AboutBlogContact
API DesignMarch 14, 2018 2 min read 21

GraphQL and the N+1 Problem: Solving it with DataLoader (2018)

AunimedaAunimeda
📋 Table of Contents

GraphQL and the N+1 Problem: Solving it with DataLoader

It’s 2018, and everyone is moving from REST to GraphQL. The promise is enticing: "Fetch exactly what you need, and nothing more." But there's a performance trap waiting for the unwary: the N+1 query problem.

What is the N+1 Problem?

Imagine a query to fetch 10 posts and their authors:

query {
  posts {
    title
    author {
      name
    }
  }
}

A naive resolver implementation would look like this:

  1. One query to fetch all posts (1 query).
  2. For each post, a separate query to fetch the author (N queries).

If you have 100 posts, you just made 101 database queries for a single request!

The Solution: DataLoader

Facebook released DataLoader, a library that uses batching and caching to solve this. Instead of fetching each author individually, DataLoader "waits" until the end of the event loop tick, collects all the requested IDs, and fetches them in a single batch query.

const DataLoader = require('dataloader');

// 1. Create a batch loading function
const batchUsers = async (ids) => {
  console.log(`Fetching IDs: ${ids}`); // Should only log once!
  const users = await db.users.find({ id: { $in: ids } });
  
  // Important: The returned array must match the order of IDs
  return ids.map(id => users.find(user => user.id === id));
};

// 2. Instantiate the loader per request
const userLoader = new DataLoader(batchUsers);

// 3. Use it in your resolver
const resolvers = {
  Post: {
    author: (post) => userLoader.load(post.authorId)
  }
};

How it Works Under the Hood

DataLoader uses process.nextTick() (in Node.js) to schedule the batch function. When userLoader.load(id) is called:

  1. It pushes the ID into a queue.
  2. It returns a Promise.
  3. Once the current execution stack is empty, the batch function is called with all queued IDs.
  4. The individual Promises are resolved with the results from the batch.

Memoization Cache

DataLoader also has a built-in cache. If you request the same author multiple times in the same request, it will return the cached Promise immediately without calling the batch function.

In 2018, building a GraphQL API without DataLoader is like driving a car with the handbrake on. It’s an essential tool for any production-grade backend.

Read Also

GraphQL Schema Design: Thinking in Graphs, Not Endpoints (2015)aunimeda
API Design

GraphQL Schema Design: Thinking in Graphs, Not Endpoints (2015)

Facebook just open-sourced GraphQL. It's time to stop thinking in REST resources and start thinking in edges and nodes. Let's design a Relay-compatible schema.

The Architecture of Resilience: Why We Abandoned 2018's Best Practices for 2026's Performanceaunimeda
Engineering

The Architecture of Resilience: Why We Abandoned 2018's Best Practices for 2026's Performance

In 2018, the industry optimized for code consistency and global state. In 2026, professional agencies optimize for data locality and the 'Cost of Change'. Here is why we transitioned from building features to architecting long-term resilience.

Postgres BML: Binary Model Loading and Vector Speed (2025)aunimeda
Database

Postgres BML: Binary Model Loading and Vector Speed (2025)

Postgres is no longer just for rows. In 2025, BML allows us to load ML models directly into the database for ultra-low latency inference.

Need IT development for your business?

We build websites, mobile apps and AI solutions. Free consultation.

Get Consultation All articles