
The first FHIRPath expression a JavaScript developer meets looks familiar and then quietly is not. Patient.name.given looks like a chain of property accesses. It behaves like a set-flattening pipeline. The gap between the two mental models is where most of the surprises live, and closing it early is a small investment that pays off every time you touch a FHIRPath expression.
The site's FHIRPath live tryout is where those expressions get exercised against a paste-in resource. For the wider setting, more on FHIR for healthcare teams collects related material.
Everything Is a Collection
The first mental shift. In JavaScript, patient.name returns whatever is there — an object, an array, undefined. In FHIRPath, every expression returns a collection. If there is one match, the collection has one item. If there are zero matches, the collection is empty. If there are many matches, the collection has all of them.
Patient.name.given in FHIRPath returns the flat list of all given names across all name entries. There is no explicit iteration. The path itself does the walk, and the collection at each step feeds into the next.
That flattening behavior is what makes short expressions expressive. It is also what breaks the JavaScript mental model at first read.
Filters Live in .where()
The FHIRPath equivalent of array.filter(x => x.use === 'official') is .where(use = 'official'). The dot invokes the filter as a method; the expression inside is evaluated against each item; matching items pass through.
Combined with the flattening pipeline, Patient.name.where(use = 'official').given returns the given names on official name entries only. That single expression replaces a small block of imperative code, and it reads left to right.
For the deeper picture, the collection semantics that surprise you at first is the entry.
Equality Is Deep, Not Reference
FHIRPath equality compares values structurally. Patient.name.family = 'Smith' returns true when any family value equals the string. There is no reference vs value confusion — the language is a data pipeline, not an object graph traversal.
That structural equality applies to complex types too. HumanName = HumanName{family: 'Smith'} is a valid expression, and it does what you would hope.
Types Are Explicit When It Matters
Patient.telecom.where(system = 'phone') returns ContactPoint items. Patient.telecom.value returns the strings. When you want to enforce a type, .ofType() filters to items of that type: Bundle.entry.resource.ofType(Patient).
The trap: .ofType() on a resource type is not the same as .as(). .ofType(Patient) filters the collection to Patient items. .as(Patient) treats a single item as a Patient for the rest of the expression. Getting them confused is the source of a real slice of head-scratching. For the pattern, reading a failing FHIRPath expression and finding the type mismatch walks through it.
Booleans Are Loose
An empty collection is falsy in a boolean context. A collection with items is truthy. A single boolean true is also truthy. That looseness makes Patient.name.exists() and Patient.name interchangeable in an if-like context, and it is one of the more compact idioms in the language.
But Patient.name.exists() = true is subtly different from Patient.name.exists(). The first is explicitly comparing to a boolean; the second is evaluated for truthiness. Most engines treat them equivalently, but strict evaluators do not. Prefer the shorter form.
String Handling
.contains() on a string returns whether the substring appears. .contains() on a collection returns whether the item exists. Same method name, different semantics based on the receiver type. That polymorphism is convenient in most cases and confusing in the edge cases.
Where FHIRPath Fits
FHIRPath is used in three places worth naming: invariants on profiles, search parameter expressions in the CapabilityStatement, and Questionnaire calculated expressions. Each is a use case where the compact syntax pays off in maintenance. For the memorized shortcuts, typical FHIRPath expressions worth memorizing collects the useful ones.
The Short Version
Every result is a collection. Filters live in .where(). Equality is structural. Types are explicit when they matter. Booleans are loose. Practice a handful of expressions against a real resource in the tryout, and the pipeline mental model settles in after fifteen minutes.

Sources
- HL7 canonical FHIRPath specification - HL7 canonical FHIRPath specification, evergreen reference