---
title: "HKSampleQuery: a one-time snapshot of matching samples"
canonical: "https://aifitnessapi.com/healthkit-queries/hksamplequery"
cluster: "HealthKit Queries"
primary_query: "HKSampleQuery"
last_reviewed: "2026-10-03"
description: "How HKSampleQuery works per Apple's docs: the initializer, HKObjectQueryNoLimit, sort identifiers that only work inside queries, and the background queue."
publisher: "AIFitnessAPI — independent, not sponsored"
cite_as: "\"HKSampleQuery: a one-time snapshot of matching samples\", AIFitnessAPI, https://aifitnessapi.com/healthkit-queries/hksamplequery"
---

# HKSampleQuery: a one-time snapshot of matching samples

> HKSampleQuery is HealthKit's general-purpose read, which Apple describes as "a general query that returns a snapshot of all the matching samples currently saved in the HealthKit store." You give it a sample type, an optional predicate, a limit and optional sort descriptors, run it with HKHealthStore's execute(_:), and receive one array of samples on a background queue. It does not keep watching the store, cannot be changed once created, and does not compute totals: use HKAnchoredObjectQuery to follow changes and HKStatisticsQuery for sums or averages.

- Canonical: https://aifitnessapi.com/healthkit-queries/hksamplequery
- Last reviewed: 2026-10-03
- Publisher: AIFitnessAPI (https://aifitnessapi.com) — independent, not sponsored
- Cite as: "HKSampleQuery: a one-time snapshot of matching samples", AIFitnessAPI, https://aifitnessapi.com/healthkit-queries/hksamplequery

---

`HKSampleQuery` is usually the first query class anyone writes, and the one most often kept for jobs it was never designed for. This page covers how it behaves, according to Apple's own documentation, and the places it quietly gives you the wrong answer. Setup, entitlements and authorization are covered in the [HealthKit integration guide](/integrate/healthkit).

## What Apple says it is

Apple's class page summarises it in one sentence: "A general query that returns a snapshot of all the matching samples currently saved in the HealthKit store." The overview adds three facts worth keeping in mind:

- It searches "any concrete subclasses of the HKSample class, including HKCategorySample, HKCorrelation, HKQuantitySample, and HKWorkout objects."
- "Sample queries are immutable: The query's properties are set when the query is first created, and they can't change."
- "As with many HealthKit classes, don't subclass the HKSampleQuery class."

**Availability.** Apple's class page lists iOS, iPadOS, Mac Catalyst, macOS, visionOS and watchOS. Its documentation data gives no introduction version for the class itself. The multi-type initializer `init(queryDescriptors:limit:resultsHandler:)` is listed from iOS 15.0, iPadOS 15.0, Mac Catalyst 15.0, macOS 13.0, visionOS 1.0 and watchOS 8.0. The type identifiers that iOS 15 added are listed on [the HealthKit types introduced in iOS 15](/healthkit-versions/ios-15).

## The initializer, parameter by parameter

The classic form is `init(sampleType:predicate:limit:sortDescriptors:resultsHandler:)`. Apple documents each argument:

| Parameter | What Apple says |
| --- | --- |
| `sampleType` | "This object can be an instance of the HKCategoryType, HKCorrelationType, HKQuantityType, or HKWorkoutType class." |
| `predicate` | Limits the results. "Pass nil to receive all the samples of the specified type." |
| `limit` | "The maximum number of samples returned by the query. If you want to return all matching samples, use HKObjectQueryNoLimit." |
| `sortDescriptors` | The order of the results. "Pass nil if you don't need the results in a specific order." |
| `resultsHandler` | Called once with the query, an array of samples ("or nil if an error occurs") and an error. |

The predicate is where most of the real decisions are made, including whether a sample that started yesterday counts as today's. That has its own page: [HealthKit query predicates](/healthkit-queries/healthkit-query-predicates).

## A minimal read

This sketch follows Apple's "Executing Sample Queries" article: one type, a date window, results sorted by start date.

```swift
import HealthKit

func readEnergyConsumed(store: HKHealthStore, start: Date, end: Date) {
    guard let energyType = HKSampleType.quantityType(forIdentifier: .dietaryEnergyConsumed) else { return }

    let window = HKQuery.predicateForSamples(withStart: start, end: end, options: [])
    let byStartDate = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)

    let query = HKSampleQuery(sampleType: energyType,
                              predicate: window,
                              limit: HKObjectQueryNoLimit,
                              sortDescriptors: [byStartDate]) { _, results, error in
        guard let samples = results as? [HKQuantitySample] else {
            // results is nil only when error is set
            return
        }
        DispatchQueue.main.async {
            // update the UI with samples
        }
    }
    store.execute(query)
}
```

The identifier used here is [`dietaryEnergyConsumed`](/healthkit/nutrition#id-dietaryenergyconsumed); converting its values means choosing an `HKUnit`, which the [HealthKit units reference](/healthkit-units) lists per type.

## Which thread the handler runs on

Apple is explicit: "Queries run on an anonymous background queue. As soon as the query is complete, the results handler is executed on the background queue. You typically dispatch these results to the main queue to update the user interface." The article version adds that the handler runs on the same background queue "but not necessarily the same thread". Touching UI state directly inside the handler is the classic crash; hop to the main queue first.

## Reading several types in one query

Since iOS 15.0 you can pass an array of `HKQueryDescriptor` objects instead of one type, using `init(queryDescriptors:limit:resultsHandler:)` or the variant that also takes sort descriptors. Apple: "Each descriptor can specify a different data type." Its own example reads step count and push count samples together. The results still come back as `[HKSample]`, so you sort them out by type yourself.

## When HKSampleQuery is the wrong class

| You need | Use instead |
| --- | --- |
| A sum, average, minimum or maximum over a window | [HKStatisticsQuery](/healthkit-queries/hkstatisticsquery) |
| One value per day or hour for a chart | [HKStatisticsCollectionQuery](/healthkit-queries/hkstatisticscollectionquery) |
| Only what changed since the last read, including deletions | [HKAnchoredObjectQuery](/healthkit-queries/hkanchoredobjectquery) |
| A signal that something changed | [HKObserverQuery](/healthkit-queries/hkobserverquery-background-delivery) |
| async/await instead of a callback | [HKSampleQueryDescriptor](/healthkit-queries/healthkit-async-query-descriptors) |
| The GPS points of a workout | [HKWorkoutRouteQuery](/healthkit-queries/hkworkoutroutequery) |
| Activity ring values | [HKActivitySummaryQuery](/healthkit-queries/hkactivitysummaryquery) |

Apple makes one comparison explicitly, on the anchored query's page: "Often, it's more efficient to set up and run a single anchored object query than to run separate sample and observer queries."

## Pitfalls

- **Sort identifiers only work inside a query.** Apple's note on `sortDescriptors`: "Use the sort descriptors you create with these identifiers only in queries. You cannot use them to perform an in-memory sort of an array of samples." To re-sort an array you already have, sort on the sample's own properties.
- **A snapshot never shows a deletion.** Running `HKSampleQuery` again and comparing the results is a poor way to sync, because a deleted sample is simply missing. Apple's page for `HKDeletedObject` says to "use HKAnchoredObjectQuery queries to generate a list of recently deleted objects." The reasoning is laid out in [incremental sync](/architecture/incremental-sync).
- **Adding up samples is not the same as asking for a total.** Apple documents that statistics queries "automatically merge the data from all of your data sources before performing the calculations". A loop over a sample array does no such merge. Read totals with [HKStatisticsQuery](/healthkit-queries/hkstatisticsquery), and see [deduplicating health data](/architecture/deduplicate-health-data) for what overlapping sources do to a naive sum.
- **The limit is not optional.** In this API `limit` is a plain `Int`, so you choose a cap or pass `HKObjectQueryNoLimit`. With a date sort descriptor and a small limit you get the first N samples in that order, which is how "the newest ten" is written; without a sort descriptor, Apple does not document which N you get.
- **An empty array is not an error.** A query that comes back with no samples and no error can mean there is no data, the predicate matched nothing, or reading was not permitted. [HealthKit returns no data](/fix/healthkit-no-data) covers how to tell those apart, and [authorization denied](/fix/healthkit-authorization-denied) covers the permission side.

## FAQ

### How do I make HKSampleQuery return every matching sample instead of a capped number?

Pass HKObjectQueryNoLimit as the limit. Apple documents it as a value indicating that the query returns all the matching samples in the HealthKit store, and the limit parameter's own description says to use it when you want every match. Any positive integer instead caps the result count, which combined with a sort descriptor on the start or end date is how you ask for only the newest or oldest few samples.

[Permalink](https://aifitnessapi.com/healthkit-queries/hksamplequery#faq-1)

### Can I sort an array of HealthKit samples with HKSampleSortIdentifierStartDate?

No. Apple's note on the sortDescriptors parameter says sort descriptors built from HealthKit's sort identifiers, such as HKSampleSortIdentifierStartDate or HKWorkoutSortIdentifierDuration, can be used only in queries and cannot be used to sort an array of samples in memory. Pass them to the query so the store returns the samples in order, or sort an existing array on the samples' own date properties.

[Permalink](https://aifitnessapi.com/healthkit-queries/hksamplequery#faq-2)

### Which thread does the HKSampleQuery results handler run on?

A background one. Apple documents that queries run on an anonymous background queue and that the results handler executes on that background queue once the query completes, though not necessarily on the same thread. Apple's guidance is to dispatch the results to the main queue before updating the user interface, so any UI work belongs inside a main-queue dispatch.

[Permalink](https://aifitnessapi.com/healthkit-queries/hksamplequery#faq-3)

### Can a single HKSampleQuery read more than one data type?

Yes, from iOS 15.0 and watchOS 8.0. The initializer init(queryDescriptors:limit:resultsHandler:) takes an array of HKQueryDescriptor objects, and Apple says each descriptor can specify a different data type. Apple's example reads step count and push count samples in one query. The results arrive as one array of HKSample objects, so your code separates them by type afterwards.

[Permalink](https://aifitnessapi.com/healthkit-queries/hksamplequery#faq-4)
