The client names the fields
GraphQL hands the shape of the response to whoever is asking for it.
Instead of your server deciding what a user endpoint returns, the client names exactly the fields it wants. The name, the picture, the last five orders with their totals. One request comes back with precisely that tree, and both the too-much and the too-little problems disappear in the same move, because the query is now the contract.
Take the schema as the second win. Every type and field is declared and inspectable. Your tooling can autocomplete queries, check them at build time, and tell you which frontend still uses the field you want to delete.
Deprecate a field, watch its usage fall to zero, and remove it. That beats versioning a whole API, which is why GraphQL APIs tend to evolve continuously instead of shipping a v2.
The bill
Now read the bill, starting with the wound everyone inflicts on themselves once. Ask for 50 orders with each restaurant's name. The naive server runs one query for the orders and 50 more for the restaurants, because each field resolves on its own.
Fix it with a batching layer that collects those 50 restaurant identifiers within one request and fetches them together. Every serious GraphQL server has one, and every team forgets to use it at least once.
Expect caching to get harder too. REST leans on HTTP caching keyed by the URL, while GraphQL posts to a single endpoint, so a CDN sees an opaque blob. You move caching into the client, or you register your queries ahead of time so they can travel as cacheable GETs again.
Put limits on how deep and how expensive a query may be, because clients can compose them freely. Skip that and eventually somebody writes the query that joins your database to itself until the pager goes off.
Worked example
Marta's team stands up an Apollo Server gateway in front of their existing REST services. The order history screen becomes one query asking for orders, restaurant names, and courier ETAs; screen load drops from 14 requests to one, and p50 falls from 1.9 seconds to 510 ms. Two weeks in, the database team flags a spike: 4,000 queries per second against the restaurants table, all single-row lookups. The gateway resolver was fetching each restaurant individually, N+1 at production scale. Dev wraps the lookup in DataLoader, which batches the 10 restaurant IDs per request into one SELECT WHERE id IN (...), and the table's query rate drops 90 percent. They also add a depth limit of 8 after an intern's test query nested orders inside restaurants inside orders and took out staging.