In Expo Router, what do usePathname and useSegments return on /product/42, and when would you reach for each?
answer
- one is the URL, one is the file
- pathname: normalized, no query
- segments: bracket names kept
- groups appear only in segments
- segments suit structural checks
basics
~20 susePathname returns the normalized URL path without the query, '/product/42', while useSegments returns the matched file segments as written, ['product', '[id]'], including any (group) names, so pathname suits display and analytics and segments suit structural checks.
solid answer
~40 s`usePathname()` from `expo-router` returns the **URL path** of the focused route with parameters filled in and no query string: on `/product/42?ref=home` it is `'/product/42'`, and route groups never appear in it. `useSegments()` returns the **file-system segments** of the matched route, not normalized: `['product', '[id]']`, and if the file lives in a group, the group too, as in `['(shop)', 'product', '[id]']`. Use the pathname for things about the concrete location: analytics screen names, share links, highlighting an active link. Use segments for **structural** decisions that must not depend on parameter values: "am I inside the `(shop)` group?", "is this any product page?", or building a link that stays inside the current tab. Both follow the globally focused route, so they re-render when it changes.
code
tsx · 11 lines// src/components/shop-banner.tsx (rendered from a layout)
import { usePathname, useSegments } from 'expo-router';
import { Text } from 'react-native';
export function ShopBanner() {
const pathname = usePathname(); // '/product/42'
const segments = useSegments(); // ['(shop)', 'product', '[id]']
if (segments[0] !== '(shop)') return null; // group check: segments only
return <Text accessibilityRole="header">You are viewing {pathname}</Text>;
}go deeper
Recall that usePathname returns the URL path with values filled in and useSegments returns the file path segments, brackets and groups included.
Explain normalization, why groups appear only in segments, and pick the right hook for analytics versus a group or route-shape check.
Keep these global hooks out of hot components, read screen params from useLocalSearchParams, and use typed segments to make structural checks refactor-safe.
Standardise how the app derives location for analytics and structural logic so route refactors do not silently break tracking or conditional UI.
## Two views of the same location Expo Router exposes the focused route in two shapes, both from `expo-router`: - **`usePathname()`** returns the current location **as a URL path**, without search parameters. Dynamic segments are **normalized**, meaning replaced by their values. - **`useSegments()`** returns the matched route **as file-system segments**, **not normalized**: bracketed names stay as written and group folders are included. For the shop app with `src/app/(shop)/product/[id].tsx` and the URL `/product/42?ref=home`: | Hook | Returns | Contains groups? | Contains query? | |---|---|---|---| | `usePathname()` | `'/product/42'` | no | no | | `useSegments()` | `['(shop)', 'product', '[id]']` | yes | no | For a catch-all docs page, `/docs/guides/install` served by `src/app/docs/[...slug].tsx`, the pathname is `'/docs/guides/install'` while the segments keep the file's own catch-all name instead of the three values. ## When the pathname is the right tool The pathname answers "**where exactly is the user?**": - screen-view analytics and logging, where `/product/42` versus `/product/43` matters; - a share button that builds a link to the current page; - marking the active item in a web-style navigation bar; - comparing against a known URL, such as `pathname === '/settings'`. Because it is normalized, the pathname is the same string a user would see in a browser's address bar. ## When segments are the right tool Segments answer "**which part of the route tree is this?**", independent of parameter values: - **Group checks** such as `segments[0] === '(shop)'`, which the pathname cannot express because groups are invisible in URLs. - **Route-shape checks** such as "is this any product page?" via `segments.includes('[id]')`, instead of a fragile regex over pathnames. - **Tab-preserving links**: a shared component can read the first segment and build an href that stays in the current group, a pattern Expo's typed-routes guide uses for a profile link reachable from two tab groups. With typed routes, `useSegments` can be narrowed with a generic listing the possible segment tuples, which turns those checks into compile-time-checked comparisons. ## Update behaviour Both hooks read the **globally focused** route. A component using them re-renders whenever navigation changes the focused route, including in background stack screens. That is fine in a layout or a small header component, but reading them in many list rows multiplies the cost. For a screen's **own** parameters, `useLocalSearchParams()` is the better source. ## Worked example across the shop app Take three URLs in the shop app, with product screens inside a `(shop)` group and a docs catch-all outside it: | URL | `usePathname()` | `useSegments()` | |---|---|---| | `/` served by `(shop)/index.tsx` | `'/'` | starts with `'(shop)'` | | `/product/42?ref=home` | `'/product/42'` | `['(shop)', 'product', '[id]']` | | `/docs/guides/install` | `'/docs/guides/install'` | `'docs'` then the catch-all's file name | Three decisions follow from the table: 1. A **"back to shop" button** should appear only inside the group: test `segments[0] === '(shop)'`. 2. **Analytics** should record `'/product/42'`, so product pages can be counted individually. 3. A rule such as "hide the cart icon on every product page" should test the segment `'[id]'` under `'product'`, so it keeps working whatever the id is. ## Common mistakes 1. **Parsing the pathname to find a parameter.** `pathname.split('/')[2]` breaks when the URL shape changes; read the parameter from `useLocalSearchParams()` instead. 2. **Looking for a group in the pathname.** `'/(shop)/product/42'` never occurs; groups exist only in segments. 3. **Comparing segments to values.** `segments[1] === '42'` never matches, because segments hold `'[id]'`, not the value. 4. **Expecting the query string.** Neither hook includes `?ref=home`; search parameters come from the params hooks. ## What interviewers look for A clear statement that one hook is the **URL** and the other is the **file path**, a concrete example of each, and at least one case where only segments work, usually a group check. Candidates who know that dynamic segments are filled in one and left bracketed in the other rarely misuse either.
- Why not read the product id with usePathname().split('/')?It couples the screen to the URL's exact shape and breaks when a segment is added or a group layout changes. `useLocalSearchParams()` returns `id` by name, already decoded, and is scoped to the screen that owns it.
- What would useSegments() return for /docs/guides/install served by src/app/docs/[...slug].tsx?The file segments, `docs` followed by the catch-all's bracketed file name, not the three URL values. The values live in `useLocalSearchParams()` as `slug: ['guides', 'install']`, while the pathname is `'/docs/guides/install'`.
saying these in an interview costs you the question
- usePathname includes the route group, such as /(shop)/product/42.
- useSegments returns the parameter values, such as ['product', '42'].
- usePathname returns the query string along with the path.
- Parsing the pathname is the recommended way to read an id.
- Both hooks return exactly the same data in different formats.