In Amplitude, what is the difference between an event property and a user property?
answer
- Two bags of key/value pairs per event
- One describes the action, one the person
- track() versus the Identify object
- Values are stamped at ingest, never rewritten
basics
~20 sAn event property describes one occurrence — the plan clicked, the search term — and is stored on that event. A user property describes the person and is stamped onto their profile, applying to events from then on.
solid answer
~40 sAmplitude stores an event stream keyed by user, and each event carries two bags of key/value pairs. **Event properties** (`event_properties`) qualify that single occurrence: `genre`, `search_term`, `cart_total`. **User properties** (`user_properties`) describe the person: `plan`, `signup_channel`, `company_size`. In the Browser SDK you set the first inline — `amplitude.track('Song Played', { genre: 'jazz' })` — and the second through an `Identify` object: `amplitude.identify(new amplitude.Identify().set('plan', 'pro'))`. The important consequence is that Amplitude stamps the user property values current at ingest time onto each event, so changing a user property does not rewrite history: events sent while the user was `free` keep `plan = free` forever. Choose event properties for facts about the action, user properties for state you want to segment and break down by.
code
javascript · 14 linesamplitude.init(API_KEY);
// event property: describes this one occurrence
amplitude.track('Order Completed', {
cart_total: 42.5,
currency: 'USD',
payment_method: 'card'
});
// user property: describes the person from now on
const id = new amplitude.Identify()
.set('plan', 'pro')
.setOnce('signup_channel', 'organic');
amplitude.identify(id);go deeper
Be ready to state the split in one sentence and name the calls: track carries event properties, the Identify object carries user properties. Give a concrete example of each from a product you have instrumented.
Explain the stamping rule — user property values are written onto each event at ingest and history is never rewritten — and what that does to a breakdown after a plan upgrade. Know set, setOnce and add.
Show judgment about taxonomy: cardinality limits on property values, few event names with rich properties, and the cost of renaming anything after months of collection. Mention tracking-plan enforcement rather than trusting code review alone.
Own the governance question. Who approves a new event, how the plan is versioned, how typed wrappers or CI checks stop drift, and how you decide what stays in Amplitude versus what only ever lives in the warehouse.
## Amplitude's data model in one paragraph Amplitude is a product-analytics platform whose entire storage model is an event stream keyed by user. Every row is an event with a name (`event_type`), a timestamp, an identity (`device_id`, `user_id`, or both), and two dictionaries of key/value pairs: **event properties** and **user properties**. Everything the product does — funnels, retention curves, behavioural cohorts, segmentation — is a query over that stream, so the quality of the answers is bounded entirely by how disciplined those two dictionaries are. ## Event properties: facts about the occurrence An event property qualifies the single thing that just happened. `Song Played` carries `genre`, `duration_seconds`, `is_offline`; `Order Completed` carries `cart_total`, `currency`, `payment_method`. The value belongs to that moment and is meaningless outside it. In the Browser SDK you pass them as the second argument to `track`: ```javascript amplitude.track('Order Completed', { cart_total: 42.5, currency: 'USD' }); ``` On the HTTP API v2 payload the same thing appears as an `event_properties` object on the event JSON. ## User properties: state about the person A user property describes the human, not the click: `plan`, `account_type`, `signup_channel`, `experiment_variant`. You set them through an `Identify` object, whose methods are the whole vocabulary — `set` overwrites, `setOnce` writes only if the property has never been set (ideal for acquisition attributes like `first_referrer`), `add` increments a numeric counter, `append`/`prepend` push onto a list, `unset` removes it: ```javascript const id = new amplitude.Identify().set('plan', 'pro').setOnce('signup_channel', 'organic'); amplitude.identify(id); ``` The HTTP API equivalent is a `user_properties` object, either on a regular event or on an `$identify` event. ## The stamping rule that trips everyone up Amplitude records the user property values that were current at ingest onto each event as it arrives. It does **not** retroactively rewrite past events when a property changes. This is a feature, not a bug: it lets you ask "how did users convert *while they were on the free plan*" rather than only "how did users who are on the free plan *today* convert". But it surprises people who expect a profile-style database where the latest value wins everywhere. If you upgrade a user from `free` to `pro`, every event they sent yesterday still reads `free`, and a breakdown by `plan` will split one human across two series. When you genuinely want current-state segmentation, segment on the user rather than breaking down the historical events, and be explicit in the analysis about which of the two you meant. ## Choosing between them Ask what the value is *about*. If the same user could produce two different values in two events on the same day, it is an event property. If it is a state that persists until something changes it, it is a user property. Two practical constraints sharpen this further: - **Cardinality.** Property values feed dropdowns and breakdowns. Raw identifiers, timestamps, free-text and URLs with query strings blow up the distinct-value count and make a breakdown unreadable. Bucket them (`price_band`, `page_type`) or leave them out of the property and keep them in the warehouse copy. - **Mutability.** A user property you rewrite hourly (say, a live score) turns into a stream of profile updates and makes historical breakdowns noisy; that value usually belongs on the event. ## Groups sit alongside both B2B products often need an account dimension: many users belong to one organisation and the question is about the organisation. Amplitude models that with **groups** — `amplitude.setGroup('org_id', 'acme')` — and group properties that live on the account rather than the person. Group-based analysis is an account-level capability rather than something every project has enabled, so confirm before designing a taxonomy that depends on it. ## Taxonomy is the real interview subject The reason an interviewer asks this is that most bad Amplitude implementations fail on the split, not on the SDK. The standard shape is **few event names, rich properties**: one `Button Clicked` with a `button_name` property beats two hundred bespoke event names, because funnels and breakdowns can slice properties but cannot un-fuse event names. Amplitude ships governance for this — a tracking plan in Amplitude Data with planned-versus-observed schemas, and the Ampli CLI, which generates a typed wrapper from the plan so a typo becomes a compile error rather than a phantom event nobody notices for a quarter. Decide the split once, write it down, and enforce it in code review; renaming a property after six months of collection means either backfilling or living with two names forever.
- When would you reach for setOnce instead of set on an Amplitude user property?Use `setOnce` for attributes that describe the user's origin and must never be overwritten by a later session — `first_referrer`, `signup_channel`, `initial_plan`. `set` overwrites on every call, so a returning user arriving from a paid ad would clobber their true acquisition channel. `setOnce` writes only if the property has never been set, preserving first-touch attribution.
- A user upgrades from free to pro. Why does a breakdown by the plan user property still show their old events under free?Amplitude stamps the user property values that were current at ingest onto each event and never rewrites history. Yesterday's events keep `plan = free`, so a breakdown splits one human across two series. That is intentional — it lets you analyse behaviour as it was at the time — but if you want current-state segmentation you must segment on the user rather than break down historical events.
- Why do experienced implementers prefer few event names with rich properties over many bespoke event names?Properties can be sliced, filtered and grouped after the fact; event names cannot be merged after the fact. One `Button Clicked` with a `button_name` property answers both the specific and the aggregate question, whereas two hundred distinct names make "how many buttons were clicked overall" impossible without listing them all, and they bloat the taxonomy that every analyst has to navigate.
Think of a receipt versus a loyalty card: the receipt records what this purchase was, the card records who the shopper is — and the receipt keeps whatever tier was printed on it that day.
saying these in an interview costs you the question
- Says changing a user property retroactively updates past events
- Treats user properties as a place for per-action values like cart total
- Creates a distinct event name per button instead of a property
- Uses raw IDs or URLs as property values, exploding cardinality
- Assumes group properties are the same as user properties