Building APIs? Your pagination strategy might be killing performance. OFFSET + LIMIT looks clean. Until your data grows. Works great with 10K rows. Breaks silently at 10M. Here’s why 👇 When someone requests : SELECT * FROM orders ORDER BY created_at DESC OFFSET 50000 LIMIT 20; The database doesn’t “jump” to row 50,000. It scans 50,000 rows. Discards them. Then returns 20. As traffic grows → Latency grows. CPU grows. Your API slows down. Now imagine a new row is inserted while the user moves from page 1 to page 2. Rows shift. Users see duplicates. Or miss records entirely. Production nightmare. The fix? Keyset (Cursor) Pagination. Instead of saying “skip 50,000 rows” Say “start after this record”. Example: SELECT * FROM orders WHERE created_at < :last_seen_timestamp ORDER BY created_at DESC LIMIT 20; Now the database: • Uses index directly • Doesn’t scan discarded rows • Avoids shifting issues • Scales predictably This is what high-scale systems use. OFFSET pagination is easy. Cursor pagination is scalable. When building backend systems, small decisions compound at scale. What’s one backend design choice you changed after hitting production load? #BackendEngineering #SystemDesign #Java #SpringBoot #Database #Performance
Optimize API Performance with Keyset Pagination
More Relevant Posts
-
Our API started timing out at page 47. Last quarter we built a reporting feature that lets users browse historical orders. Offset pagination seemed fine - LIMIT 20 OFFSET X is simple and works great early on. But once the table crossed ~5 million rows, things got weird. Users navigating to later pages were waiting 8+ seconds, and some queries started timing out entirely. We checked indexes. We blamed traffic. Both were fine. The real issue was how offset pagination works. Every query has to scan past all previous rows before returning results. Page 1 is instant. Page 1000 means the database has to walk through a huge chunk of the table first. We switched to cursor pagination: WHERE id > last_seen_id LIMIT 20 Now the database can jump directly to the next record instead of skipping millions of rows. Latency dropped from 8 seconds to under 100ms, even on deep pages. Offset pagination works well early on, but it gets expensive as datasets grow. Now pagination strategy is one of the first things I think about when designing APIs that return large datasets. what’s the worst pagination performance issue you’ve run into in production? #BackendEngineering #SystemDesign #APIs #DistributedSystems #Java #SoftwareEngineering #Scalability
To view or add a comment, sign in
-
-
🚀 Most Developers Optimize Code First. But Slow APIs Are Rarely Caused by Code. When an API becomes slow, many engineers immediately start rewriting logic or refactoring functions. In reality, performance problems usually come from three predictable bottlenecks. Whenever I investigate a slow endpoint, I start with these three. 1️⃣ The Database This is the #1 culprit in most backend systems. Common issues: • missing indexes • inefficient queries • N+1 query problems • large result sets A single slow SQL query can easily add hundreds of milliseconds to every request. Before touching application code, I always check the query execution plan. 2️⃣ Network & External Services Sometimes the API is fast — but it’s waiting on something else. Examples: • external APIs • payment gateways • microservice calls • third-party integrations One external dependency can silently add 200–500 ms to every request. This is where timeouts, caching, and resilience patterns matter. 3️⃣ Application Logic Only after checking the first two do I look at the code itself. Typical causes: • blocking synchronous calls • expensive object mapping • heavy serialization • CPU-heavy operations In .NET systems, this can also lead to thread pool starvation or high CPU usage. - 🧠 The Simple Rule I Use When an API is slow, the delay is almost always in one of these places: 1️⃣ Database 2️⃣ Network 3️⃣ Application logic Find the bottleneck first. Then optimize the right layer. Good backend engineering isn’t just about writing code. It’s about knowing where the time actually goes. #dotnet #backendengineering #performance #softwarearchitecture #webapi #csharp #microservices
To view or add a comment, sign in
-
-
🚀 System Design Series – Part 1: URL Shortener I’ve started documenting my System Design learning journey with real-world architectures. 📊 Sharing a simple diagram explaining how a URL Shortener works (like Bitly) 👇 🔍 What’s happening behind the scenes? When you create a short URL: ✔️ Request goes through a Load Balancer ✔️ Application generates a unique short code (Base62) ✔️ Mapping is stored in Database ✔️ Frequently used URLs are cached in Redis When you open the short URL: ⚡ System first checks Cache (fast response) ⚡ If not found → fetch from DB → redirect → update cache 💡 Key Learnings: - Caching reduces latency drastically - System should be highly available & scalable - Designing is about trade-offs, not complexity 📈 What I focused on while designing this: - Performance optimization - Scalability (horizontal scaling) - Fault tolerance - Clean architecture 👉 Next in series: Rate Limiter Design Would love your feedback — What would you improve in this design? #SystemDesign #Microservices #Java #Backend #Scalability #SoftwareEngineering #TechLearning #DistributedSystems
To view or add a comment, sign in
-
-
These 6 codebases taught me more than my CS degree: _____ Btw grab my 12 systems design tradeoffs sheet: https://dub.sh/12-patterns Follow Alexandre Zajac for software and AI 🫡 _____ 0. 𝗥𝗲𝗱𝗶𝘀 (simplicity as architecture): ~60K lines of C powering millions of production systems. Single-threaded by design. No clever tricks. Just proof that you don't need complexity to scale. Read ae.c — 300 lines that handle the entire event loop. 1. 𝗦𝗤𝗟𝗶𝘁𝗲 (test like lives depend on it): 150K lines of source code. 92 million lines of tests. 100% branch coverag; not line coverage, branch coverage. It runs on every phone, every browser, every OS. The testing philosophy alone is worth studying. 2. 𝗚𝗶𝘁 (data model > features): Content-addressable storage. Blobs, trees, commits, refs. Four objects run the entire system. Linus designed the data model first and features became trivial. If your system feels complicated, your data model is wrong.' 3. 𝗤𝘂𝗮𝗸𝗲 𝗜𝗜𝗜 𝗔𝗿𝗲𝗻𝗮 (performance is a design choice): Real-time 3D in 1999 on hardware weaker than your watch. Every allocation was intentional. Every branch was measured. Home of the famous fast inverse square root. This is what "optimized" actually looks like. 4. 𝗚𝗼 𝘀𝘁𝗮𝗻𝗱𝗮𝗿𝗱 𝗹𝗶𝗯𝗿𝗮𝗿𝘆 (readability is a feature): No magic. No macros. No inheritance chains. Written to be read by humans first, compilers second. Open net/http, you'll understand a production HTTP server in an hour. Now try that with Java's equivalent. 5. 𝗟𝗶𝗻𝘂𝘅 𝗸𝗲𝗿𝗻𝗲𝗹 (modularity at impossible scale): 28M+ lines. Thousands of contributors. Runs on watches, supercomputers, and Mars rovers. The VFS layer, one interface, hundreds of filesystems. Proof that good abstractions let a system grow for 30+ years. None of them are short. None of them are easy. None of them are optional if you want to get serious. Reading great code > writing more code. Pick one codebase. Spend 30 minutes a day reading it. In a month, you'll write code differently. Which codebase shaped how you think?
To view or add a comment, sign in
-
-
Data structures are fundamental for building scalable and high-performance systems. Choosing the wrong one can silently degrade performance. For example, in graph/tree transversal algorithms (BFS), a Queue is essential because it preserves FIFO (First In, First Out) behavior. When working with dynamic collections in Java, understanding internal implementations matters. LinkedList is implemented as a doubly linked list. It maintains references to the first and last nodes. When we call add(e), it internally links the new node to the last node in constant time, O(1), because it already maintains a direct reference to the last node. However, this does not mean LinkedList is always faster. Memory locality and cache efficiency often make ArrayList more performant in real-world scenarios. Random access and search operations behave differently across structures: • ArrayList contains() internally uses indexOf(), which performs a linear scan comparing values with the equals() - O(n). • LinkedList contains() also performs a linear traversal - O(n). • HashSet Backed by HashMap, it uses hashing to locate buckets, giving average O(1) lookup time (O(log n) with tree bins in Java 8+, and O(n) in worst-case collisions). Understanding these trade-offs is what allows developers to make smart structural decisions instead of default choices. These are just a few examples. What other trade-offs do you consider when choosing a data structure? #SoftwareEngineering #DataStructures #Algorithms #ComputerScience #JavaDeveloper #BackendDevelopment
To view or add a comment, sign in
-
-
Understanding Processes in Node.js: Child Process vs Cluster vs Worker Threads When building scalable backend systems in Node.js, it is crucial to understand how to handle CPU-intensive tasks and concurrency. Here are three common approaches developers use: Child Process Node.js allows you to spawn child processes using the "child_process" module. Each process runs independently with its own memory space and communicates with the parent process using IPC (Inter-Process Communication). Use cases: • Running shell commands • Heavy background tasks • Executing external scripts Cluster The cluster module allows you to create multiple Node.js worker processes that share the same server port. This helps utilize multiple CPU cores and improve application performance. Use cases: • Scaling HTTP servers • Handling large numbers of requests Worker Threads Worker threads allow running JavaScript in parallel threads within the same process. Unlike child processes, they share memory with the main thread, making them faster for CPU-heavy operations. Use cases: • Data processing • Image/video manipulation • CPU-intensive calculations Key Idea: Node.js is single-threaded for JavaScript execution, but with tools like Child Processes, Clusters, and Worker Threads, we can build highly scalable and efficient systems. Understanding when to use each approach can significantly improve the performance of backend applications. What approach do you usually use for CPU-heavy tasks in Node.js
To view or add a comment, sign in
-
💡 Real Issue → Fix: Slow APIs Even with “Good Code” Problem 🚨 Everything looked fine in code reviews… But in production: • APIs were slow under load • Response time kept increasing • CPU usage was high No obvious bugs. Logic was correct. --- What was actually happening ❌ 👉 N+1 Query Problem Example: - Fetch 1 list (say 100 items) - Then for each item → make another DB query Result: 👉 1 request = 101 DB queries Works fine locally… breaks under real traffic. --- What fixed it ✅ 👉 Optimized how data is fetched ✔ Used joins / includes instead of separate queries ✔ Batched queries where possible ✔ Selected only required fields (no over-fetching) ✔ Cached repeated reads when needed --- Simple idea: Fetch smarter, not more --- 💡 Key Insight Performance issues are often data access issues, not code issues. You don’t always need better algorithms — sometimes you just need fewer queries. --- After fixing this: ⚡ Response time dropped significantly ✔ System handled more load ✔ Backend became predictable Have you faced N+1 query issues in your apps? 🤔 #BackendDevelopment #SystemDesign #Performance #APIs #SoftwareEngineering #NodeJS
To view or add a comment, sign in
-
-
🚀 My Spring Boot API was crawling. Then I made one change and response times dropped by 60%. Let me tell you what happened. A few months ago, our team was under fire. Users were complaining. The dashboard took forever to load. We threw more servers at it. Nothing helped. Then a senior dev on our team sat down, opened the logs, and said: "We're hitting the database 47 times per request." Silence. The culprit? The classic N+1 query problem silently killing performance while we were busy blaming infrastructure. Here's what we did to fix it: ✅ Used @EntityGraph and JOIN FETCH in JPA to load related data in a single query ✅ Enabled Spring Cache (@Cacheable) for data that didn't change often ✅ Switched to async processing with @Async for non-blocking operations ✅ Paginated heavy list endpoints instead of dumping entire tables The result? API response time went from 3.2s → under 500ms. No new servers. No infrastructure cost. Just smarter code. If your Spring Boot app feels slow, don't scale blindly profile first. Tools like Spring Boot Actuator + Micrometer will show you exactly where the bottleneck lives. The best performance optimization isn't always the most complex one. Sometimes it's just asking: "How many times are we hitting the database?" ♻️ Repost if this helps someone on your team. 💬 What's your go-to Spring Boot performance tip? Drop it below! #Java #SpringBoot #BackendDevelopment #SoftwareEngineering #PerformanceTuning #TechTips
To view or add a comment, sign in
-
-
Your API is not slow. Your database is. One of the biggest mistakes I made early in my backend career: Blaming the API layer. Adding async. Changing frameworks. Tweaking code. Nothing worked. The real problem? A single bad query. We had an N+1 query issue that looked harmless in code… But in production? It exploded. 1 request → 100+ DB calls. Fix? • Proper joins • Query optimization • Adding the right indexes Result: API latency dropped from ~1.2s → 150ms 🚀 No new framework. No major rewrite. Just better database thinking. Lesson: If your API is slow, don’t start with code. Start with your queries. Because in backend systems: 👉 Database > API > Framework Curious — what’s the worst DB issue you’ve faced in production? #BackendDevelopment #SystemDesign #Databases #Python #SoftwareEngineering
To view or add a comment, sign in
-
You don't need to memorize Spring Boot annotations. You need to understand them. This carousel breaks down every important annotation across 12 categories: 🧩 Core Stereotypes — @Component, @Service, @Repository, @RestController 🌐 Web MVC — @RequestMapping, @PathVariable, @RequestBody 💉 Dependency Injection — @Autowired, @Qualifier, @Bean, @Configuration ⚙️ Config Properties — the right way to bind your application.yml 🗄️ JPA & Data — @Entity, @Query, @Modifying 🔁 Transactional — propagation types, rollback rules, the self-invocation trap ✅ Validation — @Valid, @NotBlank, @Pattern, @Size 🚨 Exception Handling — @ControllerAdvice, @ExceptionHandler ⏰ Scheduling — fixedRate vs fixedDelay vs cron, always set your timezone ⚡ Async — @Async, thread pools, CompletableFuture 🔄 Bean Lifecycle — @PostConstruct, @PreDestroy, @Scope 🔐 Security — @PreAuthorize, @PostAuthorize, @AuthenticationPrincipal Start from slide 1 and go in order — each category builds on the previous one. By the end you'll know not just what each annotation does, but exactly when and why to use it.
To view or add a comment, sign in
Explore related topics
Explore content categories
- Career
- Productivity
- Finance
- Soft Skills & Emotional Intelligence
- Project Management
- Education
- Technology
- Leadership
- Ecommerce
- User Experience
- Recruitment & HR
- Customer Experience
- Real Estate
- Marketing
- Sales
- Retail & Merchandising
- Science
- Supply Chain Management
- Future Of Work
- Consulting
- Writing
- Economics
- Artificial Intelligence
- Employee Experience
- Workplace Trends
- Fundraising
- Networking
- Corporate Social Responsibility
- Negotiation
- Communication
- Engineering
- Hospitality & Tourism
- Business Strategy
- Change Management
- Organizational Culture
- Design
- Innovation
- Event Planning
- Training & Development
Small issue. This query will first scan 50020 rows(not 50000) and then discard the first 50000 rows