Extraneous Fetching Anti-Pattern: Fetch Only What You Need
After this topic, you will be able to:
- Identify overfetching and underfetching patterns in API design
- Evaluate trade-offs between REST and GraphQL for data fetching
- Recommend field filtering and pagination strategies
- Assess the bandwidth and processing cost of extraneous data transfer
TL;DR
Extraneous fetching occurs when systems retrieve more data than needed for a specific operation—like fetching entire user profiles when only names are required. This antipattern wastes bandwidth, increases latency, and drains mobile battery life. Solutions include field projection (GraphQL, sparse fieldsets), pagination, and lazy loading. Cheat sheet: Always fetch only what you display; measure payload sizes in production; use field selection for mobile clients.
The Problem It Solves
Applications frequently retrieve far more data than they actually use, creating a cascade of performance problems. A mobile app displaying a user’s name might fetch their entire profile including address, preferences, order history, and profile images—transferring 50KB when 200 bytes would suffice. This happens because developers write SELECT * queries, use ORM defaults that eagerly load relationships, or design REST endpoints that return complete objects regardless of client needs. The pain becomes acute at scale: Instagram’s feed API initially returned full post objects with metadata, comments, and user details for every item, consuming massive bandwidth and causing slow load times on 3G networks. Mobile users suffered doubly—paying for wasted cellular data and experiencing battery drain from parsing megabytes of unused JSON. The problem compounds with pagination failures: fetching 10,000 records when displaying 20 creates memory pressure and timeout risks. Interviewers want to see you recognize this pattern in API design reviews and understand the cost implications—a 10KB overfetch across 100 million daily requests wastes 1TB of bandwidth and translates to real infrastructure costs and poor user experience on constrained devices.
Extraneous Fetching: Full Object vs Required Data
graph LR
Client["Mobile Client<br/><i>Displaying Username</i>"]
API["REST API<br/><i>/users/123</i>"]
DB[("User Database")]
Client --"1. GET /users/123"--> API
API --"2. SELECT *"--> DB
DB --"3. Full User Object<br/>50KB: name, email, address,<br/>preferences, order_history,<br/>profile_images, bio, etc."--> API
API --"4. Return 50KB"--> Client
Client -."Only Uses: name (200 bytes)<br/>Wastes: 49.8KB (99.6%)".-> Client
A mobile client requesting only a username receives a 50KB user object with complete profile data, wasting 99.6% of transferred bytes. This pattern multiplied across millions of requests creates massive bandwidth costs and poor mobile experience.
Bandwidth Cost Analysis
The mathematics of extraneous fetching reveal why this antipattern matters at scale. Consider a social media feed endpoint: fetching full user objects (10KB each with profile images, bio, follower counts) when only displaying usernames and avatars (500 bytes). Each request wastes 9.5KB. Multiply by 100 million daily requests: 950GB of wasted bandwidth daily, or 28TB monthly. At $0.08/GB for cloud egress, that’s $2,240 in unnecessary costs monthly—$26,880 annually for a single endpoint. Mobile impact amplifies the problem: users on metered data plans pay $10/GB in many markets, so that 9.5KB waste costs them $0.000095 per request. A user making 500 requests daily pays an extra $0.047 daily or $17 yearly in data charges. Battery impact is equally significant: parsing 10KB of JSON on mobile CPUs consumes 3-5x more energy than parsing 500 bytes. Studies show JSON parsing accounts for 15-20% of mobile app energy consumption in data-heavy apps. Facebook’s 2015 optimization to reduce News Feed payload sizes by 30% through field filtering resulted in 20% faster load times on 2G networks and measurably improved battery life. The lesson for interviews: always quantify waste in bandwidth, cost, and user experience metrics—not just abstract performance concerns.
Cost Impact of Extraneous Fetching at Scale
graph TB
Request["Single API Request<br/><i>User Profile Fetch</i>"]
subgraph Per Request Waste
Full["Full Object: 10KB<br/><i>profile, history, preferences</i>"]
Needed["Actually Needed: 500 bytes<br/><i>name, avatar</i>"]
Waste["Wasted: 9.5KB per request<br/><i>95% unnecessary data</i>"]
end
Request --> Full
Full --> Needed
Full --> Waste
subgraph Daily Scale: 100M Requests
Daily["Daily Waste<br/>9.5KB × 100M = 950GB"]
Monthly["Monthly Waste<br/>950GB × 30 = 28.5TB"]
end
Waste --> Daily
Daily --> Monthly
subgraph Cost Impact
Cloud["Cloud Egress Cost<br/>28.5TB × $0.08/GB<br/>= $2,280/month"]
User["User Data Cost<br/>9.5KB × 500 req/day<br/>× $10/GB = $17/year per user"]
Battery["Battery Drain<br/>Parsing 10KB vs 500 bytes<br/>= 3-5x more CPU energy"]
end
Monthly --> Cloud
Waste --> User
Waste --> Battery
The mathematics of extraneous fetching reveal massive costs at scale. A 9.5KB overfetch per request translates to $2,280 monthly in cloud egress costs, $17 yearly per user in mobile data charges, and 3-5x battery drain from unnecessary JSON parsing—demonstrating why field selection is critical for production systems.
Solution Overview
Eliminating extraneous fetching requires shifting from “fetch everything, filter client-side” to “fetch only what’s needed.” The core strategies are field projection (selecting specific fields), pagination (limiting result set size), and lazy loading (deferring non-critical data). GraphQL exemplifies this approach by letting clients specify exactly which fields they need, while REST APIs achieve similar results through sparse fieldsets (query parameters like ?fields=name,email) or custom endpoints per use case. Pagination prevents fetching thousands of records when displaying 20, using either offset-based (?page=2&limit=20) or cursor-based approaches for consistency. Lazy loading defers loading related entities until explicitly requested—fetching a user’s posts only when the user expands their profile, not preemptively. The key architectural shift is making data fetching declarative and client-driven rather than server-dictated. Netflix’s Falcor and Facebook’s GraphQL both emerged from this need: mobile clients on slow networks needed fine-grained control over payload sizes. Implementation requires backend support for field filtering, efficient database queries that project only requested columns, and monitoring to detect when clients over-request data. The goal is matching network transfer precisely to UI requirements.
Field Selection Approaches: GraphQL vs REST Sparse Fieldsets
graph TB
subgraph GraphQL Approach
GQL_Client["Client Query<br/><i>{ user(id:123) { name avatar } }</i>"]
GQL_Server["GraphQL Server<br/><i>Field Resolver</i>"]
GQL_DB[("Database")]
GQL_Client --"Declarative Fields"--> GQL_Server
GQL_Server --"SELECT name, avatar<br/>WHERE id=123"--> GQL_DB
GQL_DB --"500 bytes"--> GQL_Server
GQL_Server --"JSON: 500 bytes"--> GQL_Client
end
subgraph REST Sparse Fieldsets
REST_Client["Client Request<br/><i>GET /users/123?fields=name,avatar</i>"]
REST_Server["REST API<br/><i>Query Parser</i>"]
REST_DB[("Database")]
REST_Client --"Query Parameter"--> REST_Server
REST_Server --"SELECT name, avatar<br/>WHERE id=123"--> REST_DB
REST_DB --"500 bytes"--> REST_Server
REST_Server --"JSON: 500 bytes"--> REST_Client
end
Both GraphQL and REST sparse fieldsets achieve field selection but through different mechanisms. GraphQL uses declarative queries with type safety, while REST uses query parameters. Both result in database projection and minimal payload sizes.
How It Works
Implementing extraneous fetching prevention follows a systematic approach. Step 1: Identify what data is actually used. Instrument your frontend to log which fields from API responses are accessed. Many teams discover that 60-70% of fetched data never reaches the DOM. Netflix found their TV apps used only 30% of fields in their monolithic API responses. Step 2: Implement field selection. For GraphQL, clients specify fields in queries: query { user(id: 123) { name avatar } } fetches only name and avatar, not the full user object. For REST, add sparse fieldsets: GET /users/123?fields=name,avatar. The backend must support projection—translating field lists into database queries like SELECT name, avatar FROM users WHERE id = 123 instead of SELECT *. Step 3: Add pagination with appropriate page sizes. Analyze actual UI display limits: if showing 20 items, fetch 20-25 (small buffer for smooth scrolling). Use cursor-based pagination for consistency: GET /posts?after=cursor_xyz&limit=20 returns posts after a cursor, avoiding the “page drift” problem where offset-based pagination shows duplicates when data changes. Step 4: Implement lazy loading for expensive relationships. When fetching a user, don’t eagerly load their 500 posts. Instead, provide a separate endpoint: GET /users/123/posts. ORMs like Hibernate default to eager loading—override this with explicit lazy loading configurations. Step 5: Monitor payload sizes in production. Track P50/P95/P99 response sizes per endpoint. Set alerts when payloads exceed expected thresholds. Stripe monitors that their API responses stay under 5KB for mobile clients, escalating when endpoints bloat. Step 6: Optimize for mobile specifically. Create mobile-optimized endpoints that return minimal data: /users/123/mobile might omit large text fields and use thumbnail URLs instead of full-resolution images. Twitter’s mobile API returns tweet text without embedded media metadata, fetching media details only when users tap to expand.
Implementing Field Selection: End-to-End Flow
sequenceDiagram
participant Client as Mobile Client
participant API as API Gateway
participant Service as User Service
participant DB as Database
participant Monitor as Monitoring
Note over Client: Step 1: Identify needed fields<br/>(name, avatar only)
Client->>API: GET /users/123?fields=name,avatar
Note over API: Step 2: Parse field list<br/>Validate allowed fields
API->>Service: Request with field projection
Note over Service: Step 3: Build query with projection
Service->>DB: SELECT name, avatar<br/>FROM users WHERE id=123
DB-->>Service: 500 bytes
Service-->>API: JSON response (500 bytes)
API-->>Client: 500 bytes transferred
API->>Monitor: Log: endpoint=/users/123<br/>size=500 bytes, fields=2
Note over Monitor: Step 5: Track payload sizes<br/>Alert if >5KB threshold
Field selection implementation requires coordinated changes across the stack: client specifies fields, API validates and parses parameters, service builds projected queries, and monitoring tracks payload sizes to detect bloat over time.
Variants
GraphQL Field Selection: Clients declare exact fields needed in queries. Use when: building APIs for multiple client types (web, mobile, third-party) with varying data needs. Pros: eliminates overfetching entirely, self-documenting, enables client-driven optimization. Cons: requires GraphQL infrastructure, complex query cost analysis to prevent abuse, N+1 query risks if not using DataLoader. REST Sparse Fieldsets: Query parameters specify fields: ?fields=name,email,created_at. Use when: maintaining REST APIs but need field filtering, gradual migration from monolithic responses. Pros: simpler than GraphQL, works with existing REST infrastructure, backward compatible (default to all fields if parameter absent). Cons: less expressive than GraphQL, requires manual parameter parsing, field naming conventions needed. Projection at Database Layer: ORMs and query builders select specific columns: db.users.find({}, {name: 1, email: 1}) in MongoDB or SELECT name, email in SQL. Use when: optimizing backend performance, reducing memory usage, working with large documents. Pros: reduces database I/O, lowers memory footprint, faster serialization. Cons: requires discipline to avoid SELECT *, ORM configuration overhead, breaks when adding fields without updating projections. Pagination Variants: Offset-based (?page=2&limit=20) vs cursor-based (?after=cursor&limit=20). Use offset when: data rarely changes, simple use cases, users need random page access. Use cursor when: real-time data, consistency critical, infinite scroll UIs. Offset pros: simple, supports page numbers. Offset cons: inconsistent with data changes, expensive for large offsets. Cursor pros: consistent results, efficient at any position. Cursor cons: can’t jump to arbitrary pages, requires cursor management.
Pagination Strategies: Offset vs Cursor-Based
graph TB
subgraph Offset-Based Pagination
O_Client["Client"]
O_API["API"]
O_DB[("Database<br/><i>1000 posts</i>")]
O_Client --"GET /posts?page=2&limit=20"--> O_API
O_API --"SELECT * FROM posts<br/>OFFSET 20 LIMIT 20"--> O_DB
O_DB -."Problem: If 5 new posts added,<br/>page 2 shows duplicates from page 1".-> O_DB
O_DB --"Posts 21-40<br/>(may include duplicates)"--> O_API
O_API --"20 posts"--> O_Client
end
subgraph Cursor-Based Pagination
C_Client["Client"]
C_API["API"]
C_DB[("Database<br/><i>1000 posts</i>")]
C_Client --"GET /posts?after=cursor_xyz&limit=20"--> C_API
C_API --"SELECT * FROM posts<br/>WHERE id > decode(cursor_xyz)<br/>LIMIT 20"--> C_DB
C_DB -."Consistent: Always returns<br/>next 20 posts after cursor".-> C_DB
C_DB --"Posts after cursor<br/>(no duplicates)"--> C_API
C_API --"20 posts + next_cursor"--> C_Client
end
Offset-based pagination suffers from inconsistency when data changes between requests, showing duplicates or skipping items. Cursor-based pagination maintains consistency by using stable reference points, making it ideal for real-time feeds and infinite scroll.
Trade-offs
Flexibility vs Complexity: Returning full objects (flexibility) means clients get everything without multiple requests, but wastes bandwidth and increases latency. Field selection (complexity) requires clients to specify needs and backends to support projection, but optimizes transfer. Choose full objects for internal APIs with fast networks; choose field selection for public APIs and mobile clients. Development Speed vs Performance: Using SELECT * and ORM defaults (speed) lets developers ship features quickly without thinking about field lists, but creates performance debt. Explicit field selection (performance) requires upfront design and maintenance but prevents future scaling issues. Choose speed for MVPs and prototypes; choose performance for production systems serving millions. Consistency vs Efficiency: Eager loading related data (consistency) ensures clients have everything in one response, avoiding multiple round trips, but fetches unused data. Lazy loading (efficiency) fetches only what’s requested but requires additional requests for related data. Choose eager loading for tightly coupled data always displayed together (user + avatar); choose lazy loading for optional or rarely accessed relationships (user + full post history). Backward Compatibility vs Optimization: Maintaining existing fat endpoints (compatibility) ensures old clients don’t break but perpetuates waste. Introducing field filtering (optimization) improves performance but requires client updates. Strategy: version APIs (/v2/users with field selection) or make field filtering opt-in with sensible defaults. Monitoring Overhead vs Visibility: Tracking every field accessed (visibility) reveals optimization opportunities but adds instrumentation cost. No monitoring (overhead) keeps systems simple but leaves waste invisible. Balance: sample field usage in development/staging, monitor payload sizes in production, run periodic audits rather than continuous tracking.
When to Use (and When Not To)
Apply extraneous fetching prevention when: (1) serving mobile clients on metered or slow networks where every kilobyte matters—mobile apps should always use field selection; (2) API response sizes exceed 10KB and analysis shows <50% of fields are used—measure with browser DevTools or API analytics; (3) building public APIs consumed by third parties with unknown requirements—let them specify needs rather than guessing; (4) database queries fetch large text fields (descriptions, content) or binary data (images) not always displayed; (5) pagination is absent and endpoints return hundreds or thousands of records by default. Avoid premature optimization when: (1) building internal tools with fast networks where bandwidth is negligible; (2) data sets are inherently small (<1KB responses) and field selection adds complexity without meaningful benefit; (3) all fields are genuinely needed for every use case—don’t add field selection if 95% of requests need 95% of fields; (4) team lacks GraphQL expertise and sparse fieldsets would be poorly implemented—bad field selection is worse than none. Red flags indicating extraneous fetching: mobile app reviews complaining about data usage, API responses taking >500ms on 3G networks despite simple queries, database queries selecting all columns but serializing only a subset, ORM configurations with eager loading on all relationships, endpoints returning arrays without pagination limits, monitoring showing response sizes growing over time as fields are added but never removed. Interview anti-pattern: claiming “we’ll just use GraphQL” without understanding the implementation complexity or considering simpler alternatives like sparse fieldsets for REST APIs.
Real-World Examples
company: Facebook
system: News Feed API
implementation: Facebook’s transition from REST to GraphQL was driven by extraneous fetching problems. Their mobile apps on 2G networks in developing markets were fetching 10-15KB per feed item when only 2-3KB was displayed. The REST API returned full post objects with all reactions, complete comment threads, and user profiles. GraphQL allowed mobile clients to request only: { post { text author { name avatar } likeCount } }, reducing payload by 70%. They implemented field-level monitoring showing which fields were accessed <10% of the time, then deprecated them. The result: 30% faster feed loads on slow networks and 20% reduction in data usage. Interesting detail: Facebook’s DataLoader pattern emerged from solving N+1 queries that field selection introduced—batching requests for related data to avoid making hundreds of database calls when clients selected nested fields.
impact: 70% payload reduction, 30% faster loads on 2G networks
company: Netflix
system: Falcor API
implementation: Netflix built Falcor (a GraphQL predecessor) because their TV apps were fetching entire movie catalogs (thousands of titles with metadata) when displaying 20 items per screen. Their REST API had endpoints like /browse/action returning complete movie objects including cast lists, reviews, and high-resolution artwork URLs. Falcor let clients specify paths: ['genrelist', 'action', 0..19, ['title', 'boxart']] to fetch only titles and boxart for the first 20 action movies. Backend services used projection to query only needed fields from their Cassandra clusters. They measured 60% reduction in API response sizes and 40% reduction in backend database load. Interesting detail: Netflix discovered that 80% of their API bandwidth was consumed by artwork URLs that were never displayed because users scrolled past items—lazy loading images separately cut mobile data usage dramatically.
impact: 60% smaller responses, 40% less database load
company: Stripe
system: Payment API
implementation: Stripe’s API initially returned full charge objects with all metadata, customer details, and nested payment method information—often 8-10KB per charge. Mobile point-of-sale apps only needed charge status and amount (200 bytes). They implemented sparse fieldsets: GET /charges/ch_123?fields=amount,status,currency and monitored field usage across their API. They found that 70% of requests used <30% of available fields. For mobile SDKs, they created optimized endpoints like /charges/ch_123/mobile returning minimal data. They also implemented cursor-based pagination with limit=10 defaults instead of returning all charges. Monitoring showed average response sizes dropped from 8KB to 1.2KB for mobile clients. Interesting detail: Stripe’s API versioning strategy lets them deprecate unused fields gradually—new API versions exclude fields accessed by <5% of requests, with migration guides for affected clients.
impact: 85% payload reduction for mobile clients
Interview Essentials
Mid-Level
Explain the difference between overfetching and underfetching. Describe how to implement field selection in REST APIs using query parameters. Walk through a scenario where fetching full user objects causes performance issues and propose a solution using field projection. Demonstrate understanding of pagination basics (offset vs cursor). Show awareness that SELECT * queries are problematic at scale. Be able to calculate bandwidth waste: if 1 million requests fetch 10KB each when 1KB is needed, that’s 9GB wasted daily.
Senior
Design a field selection system for a REST API serving web and mobile clients with different data needs. Explain trade-offs between GraphQL and sparse fieldsets—when would you choose each? Describe how to detect extraneous fetching in production using monitoring (payload size tracking, field usage instrumentation). Discuss lazy loading strategies for related entities and how to avoid N+1 queries. Explain cursor-based pagination implementation and why it’s superior to offset-based for real-time data. Propose a migration strategy from fat endpoints to field selection without breaking existing clients (versioning, opt-in parameters, gradual deprecation). Calculate the cost impact: 100M requests/day, 10KB overfetch, $0.08/GB egress = $80/day = $29K/year wasted.
Staff+
Architect a company-wide solution to prevent extraneous fetching across 50+ microservices. Design monitoring and alerting systems that detect payload bloat automatically (baseline response sizes, alert on >20% growth). Propose governance: API review processes that require field usage analysis before launching endpoints, automated tools that flag SELECT * in code reviews. Discuss mobile-specific optimizations: separate mobile endpoints, image resizing at CDN edge, progressive data loading strategies. Explain how to balance developer velocity (easy to add fields) with performance (preventing bloat)—schema evolution policies, field deprecation workflows. Address organizational challenges: convincing teams to invest in field selection when “it works fine now,” quantifying user impact of wasted bandwidth on retention metrics. Design a GraphQL federation strategy where multiple teams own subgraphs but need consistent field selection patterns. Propose A/B testing frameworks to measure impact of payload reductions on user engagement and conversion rates.
Common Interview Questions
How would you detect extraneous fetching in a production API? (Answer: Monitor response payload sizes, instrument field access in clients, analyze database query patterns for unused columns, use APM tools to track serialization time)
When would you choose GraphQL over REST sparse fieldsets? (Answer: GraphQL when multiple client types with very different needs, complex nested data requirements, strong typing desired. Sparse fieldsets when maintaining existing REST APIs, simpler use cases, team lacks GraphQL expertise)
How do you implement field selection without N+1 query problems? (Answer: Use DataLoader pattern to batch requests, implement query planning to fetch related data in single queries, use database joins with projection, cache frequently accessed related entities)
What’s the difference between overfetching and chatty I/O? (Answer: Overfetching is fetching too much data per request; chatty I/O is making too many requests. Both waste bandwidth but require different solutions—field selection vs request batching)
How would you migrate a fat REST endpoint to field selection? (Answer: Version API, make field selection opt-in with sensible defaults, monitor usage of old endpoint, provide migration guide, deprecate after 6-12 months, use feature flags for gradual rollout)
Red Flags to Avoid
Claiming GraphQL solves all overfetching without understanding implementation complexity or N+1 query risks
Not quantifying the cost impact—saying ‘it wastes bandwidth’ without calculating actual numbers
Suggesting field selection for internal APIs with fast networks where complexity outweighs benefits
Ignoring mobile-specific concerns—treating mobile clients same as web despite constrained networks and battery
Not considering backward compatibility when proposing to change existing APIs—breaking clients is worse than overfetching
Recommending SELECT * as acceptable because ‘databases are fast’—shows lack of scale awareness
Key Takeaways
Extraneous fetching wastes bandwidth, increases latency, and drains mobile battery by transferring unused data—always fetch only what you display, not what you might need someday.
Solutions include GraphQL field selection, REST sparse fieldsets (?fields=name,email), database projection (SELECT name, email), pagination with appropriate limits, and lazy loading for related entities.
Calculate the cost: 10KB overfetch × 100M requests/day = 1TB wasted daily. At scale, this translates to real infrastructure costs and poor user experience on mobile networks.
Mobile clients require special attention—create mobile-optimized endpoints, use cursor-based pagination for infinite scroll, lazy load images and non-critical data, monitor payload sizes in production.
Detection strategies: monitor response sizes (alert on >20% growth), instrument field usage in clients (log which fields are accessed), analyze database queries for unused columns, track serialization time in APM tools.