Over-fetching is fetching too much data, meaning there is data in the response you don't use.
Under-fetching is not having enough data with a call to an endpoint, forcing you to call a second endpoint.
In both cases, they are performance issues: you either use more bandwidth than ideal, or you are making more HTTP requests than ideal.
In a perfect world, these problems would never arise; you would have exactly the right endpoints to give exactly the right data to your products.
These problems often appear when you scale and iterate on your products.
The data you use on your pages often change, and the cost to maintain a separate endpoint with exactly the right data for each component becomes too much.
So, you end up with a compromise between not having too many endpoints, and having the endpoints fit each component needs best. This will lead to over-fetching in some cases (the endpoint will provide more data than needed for one specific component), and under-fetching in some others (you will need to call a second endpoint).
GraphQL fixes this problem because it allows you to request which data you want from the server. You specify what you need and will get this data, and only this data, in one trip to the server.