I have never been shy of complaining about GA4. Despite being almost six years old, there are still *far* too many areas where it just doesn’t feel finished, let a lone polished.
Ignoring the user interface challenges, the consistency of data is one area that leads me towards a descent into a rage. It is almost a daily occurrence – opening two GA4 reports that should agree and finding that they don’t.
The usual suspects
Before I get to the actual point of this post (bear with me…), it is worth running through the things that genuinely do introduce bad or inconsistent data in GA4, because these are what I check first:
- Duplicate or misfiring tags. A page_view firing twice, or GA4 loading through both GTM and a hard-coded snippet, will cause all sorts of problems with inflated data.
- Consent management. If your banner blocks tags until consent is given (which it should), a chunk of traffic is either missing or being modelled rather than measured. Modelled data behaves differently from observed data and does not always break down the way you expect.
- Cross-domain tracking gaps. This needs proper configutation to avoid self-referrals, broken sessions and inflated user counts.
- Bot and spam traffic. GA4 filters known bots automatically, but this is a constant battle and plenty gets through and can confuse the data.
- Sampling. Explorations over long date ranges on busy properties get sampled. Standard reports generally don’t. That alone will make two views of the same thing disagree. It is infuriating and a good example of why you can sometimes find me screaming at GA4 – why does an exploration report need to be sampled?!!
- Data thresholding. With Google Signals enabled, GA4 suppresses rows where the numbers are small enough to risk identifying an individual. Totals then refuse to add up, with no obvious warning.
- Cardinality limits. High-cardinality dimensions get bundled into an “(other)” row once the daily limit is hit.
- Reporting identity. Blended, observed and device-based produce different user counts from the same underlying data.
- Processing latency. GA4 can take 24 to 48 hours to finish processing. Compare a fresh report against one pulled yesterday and you will invariably find that they differ.
That list covers the overwhelming majority of discrepancies I come across. Which is why the one I want to talk about caught me out.
The one that had me scratching my head
I was looking at UK traffic for a client for a monthly report and was comparing two slices of data, which I would have expected to give the same numbers. One was a view using the GB audience. The other was a standard report with a comparison applied for Country = United Kingdom.
In my naivety, I assumed that this would surely produce the same end results – it is the same country and the same date range and using the same GA4 property.
But the numbers were different.
Not wildly different – just a few percent on this occasion – but different enough that I could not put it down to rounding, and it annoyed me that I couldn’t immediately explain why there was a difference.
My first instinct was geographical. GB, surely, means Great Britain, which is England, Scotland and Wales. The United Kingdom is all of that plus Northern Ireland. Mystery solved, I thought, that will be the Northern Irish traffic, so added that to the mix.
That didn’t help, so I did some digging. It turns out that it was actually (my) user error and I learned something that I hope may be helpful for others who haven’t been through this particular learning curve.
GB and United Kingdom are the same country
GA4 stores geography in a few related dimensions. The two that matter here are:
- Country, which gives you a readable label such as “United Kingdom”
- Country ID, which gives you the ISO 3166 two-letter code, in this case “GB”
They are two views of the same field. ISO 3166-1 assigns GB to the United Kingdom of Great Britain and Northern Ireland, so despite the letters, GB is not the island of Great Britain in this context. It is the whole UK, Northern Ireland included.
There is no separate “Great Britain” value in GA4 to pick, and there is no “UK” value either. Filter on Country ID = GB and I filter on Country = United Kingdom, and we are asking for the same set of visits.
So the geography was never the problem. The problem was that I had used two different features to slice the data, and those features work in fundamentally different ways.
Audiences, comparisons and segments are three different things
This is the bit that can cause apparent data discrepancies, and it has nothing to do with maps.
Comparisons live in standard reports. They are applied at query time, as a filter over the data GA4 already holds. Apply a comparison for Country exactly matches “United Kingdom” and GA4 goes back through your historical data and shows you the matching rows. Remove it and the report goes back to normal. Nothing is stored.
Segments live in explorations. A segment is also applied at query time and retroactively, but with a crucial extra: you choose whether the segment is user-scoped, session-scoped or event-scoped. That choice changes the answer materially, and I’ll come back to it.
Audiences are a completely different animal. An audience is a stored list of users that GA4 builds going forward. Create one today and it starts collecting from today. GA4 will backfill roughly the previous 30 days where it can, and it takes a day or two to settle, but an audience is fundamentally forward-looking. It is a membership list, not a filter.
This is where the gap was coming from – it actually had nothing to do with geography at all.
Why an audience inflates the number
Audiences have a membership duration. The default is 30 days but it can be set as high as 540. Once a user qualifies, they stay on that list for the full duration whether or not their later behaviour still matches.
Somebody browses your site from Manchester on the 3rd. GA4 sees a GB event, they meet the condition, and they are added to the audience. On the 12th, they fly to Paris for work and visit your site again from the hotel wifi. That session is recorded as France. But the user is already a member of the GB audience and will stay a member for the rest of the membership duration, so their French sessions land in the GB numbers too.
A session-scoped comparison for United Kingdom handles the same person differently. It looks at each session on its own terms, counts the Manchester one and ignores the Paris one, because the comparison is a filter over the data rather than a list of people.
The important distinction is what each one is actually answering. The comparison tells you how much activity happened in the UK. The audience tells you how much activity came from people who have been in the UK at some point recently. Those are two different questions, and the second will always return the larger answer.
The reverse case is the one that is potentially more troublesome. Somebody based in Frankfurt connects through a UK VPN once, or spends a week in London, or happens to be routed through a corporate network that resolves to a British IP. That single event puts them in the GB audience, and every session they have for the next 30 days counts as UK traffic even though they never come near our shores.
This issue obviously gets worse if you have a really long membership duration. If you opt for the maximum 540 days, you end up with a situation where people are in a country audience for a year and a half, even if they were only actually in that country for a day.
The size of the gap depends on how mobile your users are (in the sense of moving around themselves, not using a mobile device!). A local, physical business will most likely be largely unaffected by it over a short time period. A B2B software company with a customer base of people who fly a lot will see a lot more of it.
None of this is a bug. It is exactly what audiences are supposed to do. If I am running a remarketing campaign, I want to keep advertising to somebody who showed UK buying intent last week, and I do not want them dropping out of my list the moment they board a flight.
Stickiness is a feature. It is just a feature that makes audiences a poor instrument for counting things, and that is the mistake I made.
Using a GB Audience was causing the discrepancy in my numbers.
The other things that pull the numbers apart
Once you have ruled out the audience-versus-comparison issue, a handful of smaller factors can still shift the total.
Scope: Country is recorded per event. A user can have events attributed to more than one country, so a user-scoped UK definition and a session-scoped one will disagree, with users normally coming out higher. If you are reporting on traffic rather than people, session scope is almost always what you want.
Homemade definitions of ‘UK’: I have seen “UK” built by picking regions: England, Scotland and Wales. That quietly drops Northern Ireland, which is the mistake I very nearly talked myself into. I have also seen it built to include the Isle of Man, Jersey and Guernsey. Those are Crown Dependencies with their own ISO codes (IM, JE, GG), so GA4 treats them as separate countries, as it does Gibraltar (GI). Neither approach is wrong exactly, but each gives a different answer, and if two people in the same business have built it differently you will never reconcile the reports.
Operator choice: “Contains United Kingdom” and “exactly matches United Kingdom” might behave the same today, but contains is fragile and will happily catch things you did not intend if the value list ever changes. It is always safer to use exact matching.
Localised labels: The country name is a display label. Pull the data through the API, Looker Studio, or a property set to a different reporting language, and the string can come back differently. The ISO code does not change.
(not set). A small slice of traffic never resolves to a country at all. It sits outside both your GB and your United Kingdom definitions, so neither will ever tally to your all-traffic total.
How to reliably report on UK traffic in GA4
If the goal is a consistent, defensible view of UK traffic that survives handovers and cross-checks, this is what I would recommend:
Use a session-scoped segment or comparison, not an audience. Audiences are for remarketing. Anything you are going to report on or make decisions from should be defined at query time so it is retroactive and reproducible.
Filter on Country ID exactly matches GB. It captures the same traffic as the country name, and the code is stable across languages, tools and Google’s occasional relabelling. If you are working exclusively in the GA4 interface and the readable name is easier for colleagues to follow, Country exactly matches “United Kingdom” is fine. Just pick one and stick to it.
Decide explicitly on the Crown Dependencies. For most businesses, GB alone is right. If your commercial definition of the UK needs the Isle of Man and the Channel Islands, build it as GB, IM, JE, GG and write that down somewhere. You just need to make sure that everyone is sharing the same approach as it *will* result in data discrepancies unless you all use the same definition.
Define it once and reuse it. Save it as a comparison in GA4, mirror the same logic as a filter in Looker Studio, and if you have BigQuery export, build it as a view. One definition, three places, same number.
Document it in plain language. A single line in the reporting notes, something like “UK = Country ID GB, session scope, Crown Dependencies excluded”, will save more time over a year than any amount of retrospective reconciliation.
TL;DR
The short story (sorry – I have deep dived this one) = I was scratching my head trying to diagnose what I thought was a tracking problem, where there actually wasn’t one.
The data was fine. My two definitions were not the same definition, but the labels were just similar enough to convince me they were. Hence my confusion (and, in this case, unjustified rage).
So when two GA4 numbers refuse to agree, check the usual suspects by all means. But check what you actually asked for first. More often than I would like to admit, that is where the answer is.