<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/it-worked-in-dev-it-worked-in-qa-then-production-happened--hw3se8sih" -->

---
title: It Worked in Dev. It Worked in QA. Then Production Happened.
description: A backend engineer shares a production incident where an appointment-fetching endpoint worked fine in dev and QA but caused 4-second response times in...
canonical: https://daily.dev/posts/it-worked-in-dev-it-worked-in-qa-then-production-happened--hw3se8sih
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: It Worked in Dev. It Worked in QA. Then Production Happened. | daily.dev
og:description: A backend engineer shares a production incident where an appointment-fetching endpoint worked fine in dev and QA but caused 4-second response times in...
og:url: https://daily.dev/posts/it-worked-in-dev-it-worked-in-qa-then-production-happened--hw3se8sih
og:image: https://api.daily.dev/og/posts/Hw3SE8SiH.png
og:image:alt: It Worked in Dev. It Worked in QA. Then Production Happened.
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# It Worked in Dev. It Worked in QA. Then Production Happened.

**[Developer's Journey](https://daily.dev/sources/devjourney)** · [@nikhil3](https://daily.dev/nikhil3) · 4 min read · 617 upvotes · 61 comments

## Summary

A backend engineer shares a production incident where an appointment-fetching endpoint worked fine in dev and QA but caused 4-second response times in production. The issue was an N+1 query problem: the code made 6,000+ individual database calls to fetch patient details. The solution involved batching patient data retrieval into a single query using in-memory maps and adding proper projections, reducing latency to 500-600ms. The incident highlights the importance of testing with realistic data volumes, thorough code reviews, and anticipating edge cases during development.

## Content

It’s interesting how we approach problems at different stages of the software development life cycle.

A few months back at Medoc (where I am currently working as a backend engineer, and since it’s a growing startup, a lot of things are being handled by me), I asked my teammate (a fresher) to write an endpoint for fetching today’s appointments and those that are not completed for a doctor.

It was quite simple because we have a separate collection for appointments with an eventDate field, so he just needed to query it based on the current time. But here was the catch: the frontend also required patient details along with the appointments. The patient data is stored in a separate database.

So it became a case of dual database calls, where the first call fetches the appointments and the second call fetches the associated patient details, which is a typical N+1 query problem.

What my teammate did, like most people would, was:

- fetch appointments
- loop over each appointment
- fetch patient details one by one

And it worked.

Manual testing passed, it was deployed to dev for QA testing, and that passed too.

Soon it was deployed to production and worked fine for over two months. Everything seemed sorted.

Then a few days back, I got a complaint from the frontend team that their home screen was getting stuck, and that too for only one doctor, which was strange. A frontend friend and I started debugging it, but here we made a mistake. We were testing in the dev environment and were not able to reproduce the issue.

We spent almost an hour trying to fix and optimize the UI. Our initial thought was that since it was a home screen, it was hitting too many endpoints while loading the initial data. But we couldn’t find the real culprit.

Then we checked the production data for that doctor. Once we accessed the user account and opened the appointments view, we found the issue.

This particular doctor had 6,000+ appointments, which is a lot for a startup, and he had marked only a very small number as completed, so the incoming data was huge.

I immediately started pinpointing the API responsible for fetching these appointments. As soon as I looked at its implementation, the problem became clear.

It was making 6,000+ database calls to retrieve patient data. That’s why the response time was around 4 seconds, which was never caught during initial testing because we never tested with this much data.

I immediately refactored the code using in-memory maps (feel free to comment if you have a better approach).

The implementation was simple:

- first, fetch all required appointments
- then create an array of patient IDs
- fetch all patient details in a single database call
- create a map with patient ID as the key and patient details as the value
- loop over the appointments again to construct the DTO

I also optimized a few things. This route was over-fetching data, so I added proper projections and fetched only the required fields. This reduced the payload size as well.

After this, the latency came down to 500–600 ms, which was a huge difference.

Now comes the real lesson.

The fault was not just the developer’s. It was also:

- me, for merging the PR without a deep review and not clarifying constraints
- the tester, who tested only in a controlled environment
- the developer, who didn’t think about edge cases or ask for clarity

But I believe these kinds of unexpected production issues are what make developers grow and face the real horror of production.

While writing code, always expect the worst and hope for the best.

— Nikhil

## Community discussion

Top comments from developers on daily.dev.

**@astitvarai** · 37 upvotes

> I think Pagination and Indexing in DB can do this even better

**@kushagrabharti** · 28 upvotes

> These are the experiences that truly shape developers. Great post 👏

**@pilinux** · 12 upvotes

> Let's apply this situation to a 5-second window.
>
> In your latest solution, imagine you have 50k patients and 1k doctors. At a given time, all 1k doctors call the API to retrieve incomplete appointments. Assume that 1 KB of memory is required to load one patient’s details.
>
> In parallel, for each doctor, the API fetches and holds (50k × 1 KB) = 50 MB of data for further processing. For 1k doctors, this results in approximately 50 GB of memory usage.
>
> In reality, the memory usage would likely be even higher due to overhead from data structures, object metadata, and buffering.

**@jaison1** · 7 upvotes

> Paginate the results.

**@marcuslma** · 6 upvotes

> Great post. This is real life for developers. This experience is common for every dev. Unfortunately, users always surprise us by using the system in unexpected ways. I’ve been through this many times, but the good thing is that we always come out better devs.

## Similar posts on daily.dev

- [We Fixed N\+1 With Eager Loading. Memory Usage Tripled.](https://daily.dev/posts/we-fixed-n-1-with-eager-loading-memory-usage-tripled--4oqpykzth) · Medium · 39 upvotes · 6 comments

---

Tags: [#performance](https://daily.dev/tags/performance), [#testing](https://daily.dev/tags/testing), [#database](https://daily.dev/tags/database), [#backend](https://daily.dev/tags/backend)

[View this post on daily.dev](https://daily.dev/posts/it-worked-in-dev-it-worked-in-qa-then-production-happened--hw3se8sih)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"DiscussionForumPosting","mainEntityOfPage":"https://daily.dev/posts/it-worked-in-dev-it-worked-in-qa-then-production-happened--hw3se8sih","headline":"It Worked in Dev. It Worked in QA. Then Production Happened.","text":"A backend engineer shares a production incident where an appointment-fetching endpoint worked fine in dev and QA but caused 4-second response times in production. The issue was an N+1 query problem: the code made 6,000+ individual database calls to fetch patient details. The solution involved batching patient data retrieval into a single query using in-memory maps and adding proper projections, reducing latency to 500-600ms. The incident highlights the importance of testing with realistic data volumes, thorough code reviews, and anticipating edge cases during development.","url":"https://daily.dev/posts/it-worked-in-dev-it-worked-in-qa-then-production-happened--hw3se8sih","datePublished":"2026-01-11T18:40:09.010Z","dateModified":"2026-01-11T18:40:26.599Z","author":{"@type":"Person","name":"Nikhil","url":"https://daily.dev/nikhil3","image":"https://avatars.githubusercontent.com/u/123929646?v=4","description":"Backend Engineer | Building and Scaling Real-World APIs | Learning From Production","worksFor":{"@type":"Organization","name":"Medoc Health","logo":"https://res.cloudinary.com/daily-now/image/upload/s--tlVupH_6--/f_auto/v1726094825/companies/medochealth"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"EndorseAction"},"userInteractionCount":1020}},"image":"https://media.daily.dev/image/upload/s--LE8-PP_3--/f_auto/v1768156007/posts/CYa09mOAH?_a=BAMAMiZW0","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":617},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":61}],"comment":[{"@type":"Comment","text":"I think Pagination and Indexing in DB can do this even better","datePublished":"2026-01-13T09:00:07.174Z","url":"https://daily.dev/posts/Hw3SE8SiH#c-sdrzr6IC8","author":{"@type":"Person","name":"Astitva Rai","url":"https://daily.dev/astitvarai","image":"https://lh3.googleusercontent.com/a/ACg8ocKIYZnCc49pxAq8ieqEZ7qN2R72EcT6y_Ke2B4Za63akFuCVGya=s96-c"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":37}},{"@type":"Comment","text":"These are the experiences that truly shape developers. Great post 👏","datePublished":"2026-01-12T06:55:12.761Z","url":"https://daily.dev/posts/Hw3SE8SiH#c-FjRoZHatm","author":{"@type":"Person","name":"KUSHAGRA BHARTI","url":"https://daily.dev/kushagrabharti","image":"https://avatars.githubusercontent.com/u/184737684?v=4"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":28}},{"@type":"Comment","text":"Let’s apply this situation to a 5-second window.\nIn your latest solution, imagine you have 50k patients and 1k doctors. At a given time, all 1k doctors call the API to retrieve incomplete appointments. Assume that 1 KB of memory is required to load one patient’s details.\nIn parallel, for each doctor, the API fetches and holds (50k × 1 KB) = 50 MB of data for further processing. For 1k doctors, this results in approximately 50 GB of memory usage.\nIn reality, the memory usage would likely be even higher due to overhead from data structures, object metadata, and buffering.","datePublished":"2026-01-13T18:00:27.228Z","url":"https://daily.dev/posts/Hw3SE8SiH#c-ZXrst8NJl","author":{"@type":"Person","name":"mahir","url":"https://daily.dev/pilinux","image":"https://avatars.githubusercontent.com/u/13842104?v=4"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":12}},{"@type":"Comment","text":"Paginate the results.","datePublished":"2026-01-13T12:51:10.724Z","url":"https://daily.dev/posts/Hw3SE8SiH#c-ecbhpzU9c","author":{"@type":"Person","name":"Jaison","url":"https://daily.dev/jaison1","image":"https://media.daily.dev/image/upload/s--rFX07uog--/f_auto/v1723480345/avatars/avatar_gqE0GqQSymorGTzq3NLzx"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":7}},{"@type":"Comment","text":"Great post. This is real life for developers. This experience is common for every dev. Unfortunately, users always surprise us by using the system in unexpected ways. I’ve been through this many times, but the good thing is that we always come out better devs.","datePublished":"2026-01-13T13:06:01.571Z","dateModified":"2026-01-13T13:07:26.933Z","url":"https://daily.dev/posts/Hw3SE8SiH#c-BMERMlTgT","author":{"@type":"Person","name":"Marcus Maurmann","url":"https://daily.dev/marcuslma","image":"https://lh3.googleusercontent.com/a/ACg8ocJGnRFOszBmvRf2NyBZQdIeajRf7toRSiFjapRidyNrlJZMWO7M=s96-c"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":6}}],"isPartOf":{"@type":"WebPage","url":"https://daily.dev/squads/devjourney","name":"Developer's Journey"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Developer's Journey","item":"https://daily.dev/squads/devjourney"},{"@type":"ListItem","position":3,"name":"It Worked in Dev. It Worked in QA. Then Production Happened."}]}
```

