Skip to content

Performance Comparison — REST vs GraphQL

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you'll learn about Performance Comparison. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

REST and GraphQL have different performance profiles. REST excels with HTTP caching and simple queries. GraphQL shines with complex data requirements but adds query Parsing and resolver overhead.

What You'll Learn

By the end of this lesson, you will measure and compare REST and GraphQL performance, understand when each is faster, and apply optimization techniques.

Why It Matters

Choosing the wrong API style for your data access patterns can multiply server load by 10x. Understanding performance trade-offs prevents costly architectural mistakes.

Real-World Use

Facebook migrated to GraphQL because REST was causing 10+ round trips per feed load. Their internal benchmarks showed GraphQL reduced data transfer by 70% for complex screens.

Performance Factors

flowchart TD
    Query[Client Query] --> Complexity{Query Complexity}
    Complexity -->|Simple, Single Resource| REST[REST Faster]
    Complexity -->|Complex, Nested| GQL[GraphQL Faster]
    REST --> Cache[HTTP Caching]
    REST --> Overfetch[Over-fetching]
    GQL --> NPlus1[N+1 Problem]
    GQL --> Parse[Query Parsing Overhead]

Latency Benchmark

# latency_bench.py
import time
from typing import Any, Callable, Dict, List

class APIBenchmark:
    def __init__(self):
        self.rest_times: List[float] = []
        self.gql_times: List[float] = []

    def rest_simulate(self, n_endpoints: int, base_ms: float = 50) -> float:
        total = 0
        start = time.time()
        for _ in range(n_endpoints):
            time.sleep(base_ms / 1000)
            total += base_ms
        elapsed = time.time() - start
        self.rest_times.append(elapsed * 1000)
        return elapsed * 1000

    def gql_simulate(self, n_resolvers: int, resolver_ms: float = 10) -> float:
        start = time.time()
        time.sleep(5 / 1000)  # query parse
        for _ in range(n_resolvers):
            time.sleep(resolver_ms / 1000)
        elapsed = time.time() - start
        self.gql_times.append(elapsed * 1000)
        return elapsed * 1000

    def compare(self, scenario: str, n_calls: int, n_resolvers: int):
        rest_time = self.rest_simulate(n_calls)
        gql_time = self.gql_simulate(n_resolvers)
        print(f"{scenario:30s} REST={rest_time:.0f}ms GQL={gql_time:.0f}ms "
              f"({'GQL' if gql_time < rest_time else 'REST'} faster)")

bench = APIBenchmark()
bench.compare("Simple single resource", n_calls=1, n_resolvers=1)
bench.compare("Profile (user + orders)", n_calls=3, n_resolvers=3)
bench.compare("Dashboard (5 resources)", n_calls=5, n_resolvers=8)
bench.compare("Feed (20 items + authors)", n_calls=21, n_resolvers=25)

Expected output:

Simple single resource          REST=50ms GQL=15ms (GQL faster)
Profile (user + orders)          REST=150ms GQL=35ms (GQL faster)
Dashboard (5 resources)          REST=250ms GQL=85ms (GQL faster)
Feed (20 items + authors)        REST=1050ms GQL=255ms (GQL faster)

Payload Size Comparison

# payload_bench.py
import json
from typing import Any, Dict, List

class PayloadBenchmark:
    def __init__(self):
        self.full_user = {
            "id": 1, "name": "Alice", "email": "a@x.com",
            "phone": "555-0100", "address": "123 St",
            "age": 30, "role": "admin", "status": "active",
            "created_at": "2024-01-01T00:00:00Z",
            "updated_at": "2024-06-01T12:00:00Z",
            "bio": "Developer", "timezone": "UTC", "avatar": "url",
        }

    def rest_payload(self, fields_needed: int) -> int:
        payload = self.full_user if fields_needed > len(self.full_user) else self.full_user
        return len(json.dumps(payload))

    def gql_payload(self, fields_needed: int) -> int:
        selected = dict(list(self.full_user.items())[:fields_needed])
        return len(json.dumps(selected))

    def compare(self):
        for needed in [2, 5, 10, 15]:
            rest = self.rest_payload(needed)
            gql = self.gql_payload(needed)
            ratio = rest / gql
            print(f"{needed:2d} fields needed: REST={rest}B GQL={gql}B ratio={ratio:.1f}x")

bench = PayloadBenchmark()
bench.compare()

Expected output:

 2 fields needed: REST=<n>B GQL=<n>B ratio=<n>.x
 5 fields needed: REST=<n>B GQL=<n>B ratio=<n>.x
10 fields needed: REST=<n>B GQL=<n>B ratio=<n>.x
15 fields needed: REST=<n>B GQL=<n>B ratio=<n>.x

Server Load Analysis

# server_load.py
from typing import Any, Dict, List

class ServerLoadAnalysis:
    def __init__(self):
        self.query_count = 0

    def rest_request(self, path: str) -> Dict:
        self.query_count += 1
        return {"path": path}

    def gql_resolve(self, fields: List[str]) -> Dict:
        self.query_count += len(fields)
        return {f: f"<{f}>" for f in fields}

    def reset(self):
        self.query_count = 0

analysis = ServerLoadAnalysis()

# REST: 1 endpoint per resource
analysis.rest_request("/user")
analysis.rest_request("/user/orders")
analysis.rest_request("/user/friends")
print(f"REST queries: {analysis.query_count}")

analysis.reset()

# GraphQL: resolvers per field
analysis.gql_resolve(["id", "name", "email", "orders", "friends", "settings"])
print(f"GraphQL resolver calls: {analysis.query_count}")

Expected output:

REST queries: 3
GraphQL resolver calls: 6

Common Mistakes

1. Assuming GraphQL is Always Faster

Simple single-resource fetches are often faster with REST because GraphQL adds query parsing overhead.

2. Ignoring N+1 in Performance Benchmarks

Without DataLoader, GraphQL benchmarks hide N+1 queries. Always measure resolver-level database calls.

3. Benchmarking Only on Localhost

Localhost benchmarks ignore network latency where GraphQL's single-call advantage shines.

4. Not Measuring Cached Responses

REST with HTTP caching outperforms GraphQL for repeated reads. Include cache hit ratios in comparison.

5. Overlooking Serialization Cost

Large GraphQL responses can take longer to serialize. Use streaming and pagination for large datasets.

Practice Questions

1. When is REST faster than GraphQL?

For simple single-resource fetches. REST avoids query parsing and can leverage HTTP caching.

2. When is GraphQL faster?

For complex screens needing multiple related resources. One GraphQL call replaces 3-10 REST calls.

3. What is the main server-side cost of GraphQL?

Query parsing, validation, and resolver execution. Each field triggers a resolver function.

4. How does caching affect the performance comparison?

REST can cache at CDN level. GraphQL needs resolver-level caching, which is more complex to configure.

Challenge

Build a performance benchmark that measures REST vs GraphQL for an e-commerce product page showing product details, reviews, related products, and seller info.

FAQ

Is GraphQL slower on the server?

Yes, generally. GraphQL adds query parsing and validation. REST handlers are simpler functions.

How much overhead does GraphQL parsing add?

Typically 2-10ms for simple queries, more for complex nested queries. Negligible compared to database time.

Does GraphQL use more CPU?

Yes, because resolvers run per field. Use DataLoader to batch database calls and reduce CPU overhead.

How do I reduce GraphQL response time?

Use persisted queries, DataLoader, resolver caching, query complexity limits, and pagination.

Which is better for high-traffic public APIs?

REST with CDN caching handles read-heavy workloads better. GraphQL suits complex internal APIs.

Mini Project: Performance Tester

# performance_tester.py
import time
from typing import Any, Callable, Dict, List

class PerformanceTester:
    def test_rest(self, n_calls: int, handler: Callable) -> float:
        start = time.time()
        for _ in range(n_calls):
            handler()
        return (time.time() - start) * 1000

    def test_graphql(self, n_queries: int, queries: List[str], handler: Callable) -> float:
        start = time.time()
        for q in queries * n_queries:
            handler(q)
        return (time.time() - start) * 1000

tester = PerformanceTester()
rest_time = tester.test_rest(100, lambda: None)
gql_time = tester.test_graphql(100, ["{users{name}}"], lambda q: None)
print(f"REST x100:   {rest_time:.2f}ms")
print(f"GraphQL x100: {gql_time:.2f}ms")

Expected output:

REST x100:   <n>.ms
GraphQL x100: <n>.ms

What's Next

You understand performance comparison. Next, explore use cases, then hybrid REST + GraphQL approach.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro