In a browser, how do you use performance.mark() and performance.measure() to time a block of your own application code, and where do the results end up?
answer
- timestamps your own code contributes
- one call starts, another spans
- a named duration between two points
- entryType 'mark' and entryType 'measure'
- getEntriesByType or a PerformanceObserver
basics
~10 sperformance.mark() records a named timestamp; performance.measure() creates a named duration between two marks. Both become entries in the browser's performance timeline, readable with performance.getEntriesByType('mark'/'measure') or a PerformanceObserver, and visible in a DevTools performance recording.
solid answer
~50 sThe User Timing API gives you two primitives. `performance.mark('boot:start')` drops a named, high-resolution timestamp into the performance timeline; after the work finishes you drop `performance.mark('boot:end')` and call `performance.measure('boot', 'boot:start', 'boot:end')`, which creates a named entry whose `duration` is the gap between them. The returned `PerformanceMeasure` object has `name`, `startTime` and `duration`, and the same entries can be pulled later with `performance.getEntriesByType('measure')` or streamed live to a reporting function through a `PerformanceObserver` observing types `mark` and `measure`. Because the entries live in the browser's own timeline, they also render as labelled bars in a DevTools performance trace, lined up against the network and main-thread tracks — which is the real payoff versus wrapping the code in `Date.now()` arithmetic. In a long-lived app, clear the marks you no longer need so the timeline buffer does not grow unbounded.
code
javascript · 16 linesfunction applyFilters() {
let total = 0;
for (let i = 0; i < 1e6; i++) total += i;
return total;
}
performance.mark('filter:start');
applyFilters();
performance.mark('filter:end');
const measure = performance.measure('filter', 'filter:start', 'filter:end');
console.log(measure.name, measure.entryType, measure.duration);
performance.clearMarks('filter:start');
performance.clearMarks('filter:end');
performance.clearMeasures('filter');go deeper
Be ready to write the three lines from memory: mark the start, mark the end, measure between them by name. Say plainly that the result is an entry in the browser's timeline, not a console log.
Explain the two ways to read the entries — pulling with getEntriesByType versus pushing through a PerformanceObserver — and why timestamps are relative to timeOrigin rather than the wall clock.
Show judgment about instrumentation as a product: stable measure names that become dashboard keys, clearing entries so a long-lived tab does not accumulate them, and keeping marks out of hot loops.
Own the convention across teams — who gets to add a measure, what the naming scheme is, and how app-level phases are reconciled with the browser's own metrics so a dashboard has one vocabulary instead of five.
## What the User Timing API is The browser already records timestamps for things it does itself — the navigation, every subresource fetch, paint milestones. The User Timing API lets *your* code contribute entries to that same timeline, so application-level phases ("hydrate", "first render of the dashboard", "filter recomputed") sit next to the browser's own events in one ordered list of `PerformanceEntry` objects. Every entry has four common fields: `name`, `entryType`, `startTime` and `duration`. A mark has `entryType: 'mark'` and a `duration` of 0. A measure has `entryType: 'measure'` and a real duration. ## Marks `performance.mark(name)` records the current high-resolution time under a name. "High resolution" means a `DOMHighResTimeStamp`: a floating-point number of milliseconds measured from `performance.timeOrigin` (the moment the document's time began), not a Unix-epoch value. In modern browsers the call also *returns* the created `PerformanceMark`, so you can use it immediately. Marks are not unique. Marking the same name twice creates two entries; both are kept, and `performance.measure` resolving a mark by name uses the most recent one with that name. If you need extra context on the entry, `performance.mark('x', { detail: { userId: 7 } })` attaches arbitrary structured data. ## Measures `performance.measure(name, startMark, endMark)` computes the interval between two named marks and stores it as one entry. Both mark arguments are optional in useful ways: - `performance.measure('boot')` — measures from the start of the timeline to now. - `performance.measure('boot', 'boot:start')` — measures from that mark to now, which is the common shape when you only bothered to mark the beginning. There is also an options form, `performance.measure(name, { start, end, duration, detail })`, where `start`/`end` may be mark names *or* raw timestamps. That matters when the numbers came from somewhere else — a server-provided timing, or a value you already captured with `performance.now()`. ```javascript performance.mark('filter:start'); applyFilters(); performance.measure('filter', 'filter:start'); ``` ## Reading the results Two ways, and they are not interchangeable: - **Pull.** `performance.getEntriesByType('measure')` or `performance.getEntriesByName('boot')` returns what is currently in the buffer. Simple, but you have to choose a moment to look, and you may look too early. - **Push.** A `PerformanceObserver` observing `{ entryTypes: ['mark', 'measure'] }` invokes your callback as entries are created. This is what a real-user-monitoring script uses, because it never has to guess when the work finished. ```javascript new PerformanceObserver((list) => { for (const entry of list.getEntries()) { report(entry.name, entry.duration); } }).observe({ entryTypes: ['measure'] }); ``` ## Where the entries show up Beyond your own code, marks and measures are surfaced by tooling. A performance recording in browser devtools renders measures as named bars on a timings track, aligned with the main thread — so a 900 ms "hydrate" measure can be read directly against the long task that produced it. Some analytics and RUM vendors also collect `measure` entries automatically, which makes User Timing a cheap way to get app-specific phases into a dashboard without inventing a transport. ## Why not just subtract timestamps `const t = Date.now()` then `Date.now() - t` gives you a number, and nothing else. It is millisecond-granular, it is wall-clock time (subject to the system clock changing under you), and it produces no timeline entry, so no tool can see it. `performance.now()` fixes the precision and monotonicity problems but still produces no entry. Marks and measures are the version that other tools can consume. ## Hygiene The timeline buffer is finite and shared. In a single-page app that marks something on every route change, entries accumulate for the life of the tab. Call `performance.clearMarks(name)` and `performance.clearMeasures(name)` once you have reported a phase, or the entry list you later query gets long and the retained detail objects keep references alive. Also note that marking is not free-of-charge instrumentation you can sprinkle everywhere at maximum density: each call does real work. The cost is small enough for phase-level instrumentation and far too high inside a hot loop that runs thousands of times per frame. ## Naming Pick a convention and keep it, because these names become dashboard keys. A common one is `area:phase` — `checkout:start`, `checkout:end`, measure `checkout`. Keep the measure name stable even if the mark names change; the measure is the thing you will chart.
- When would you use performance.now() instead of a mark?When you only need a number in-process and nobody else will consume it — a quick delta inside a function, or a value you will feed into `performance.measure`'s options form later. `performance.now()` returns a high-resolution timestamp but creates no timeline entry, so devtools and RUM collectors cannot see it. If the timing is worth reporting or worth seeing on a trace, make it a mark and a measure instead.
- What happens if you call performance.measure with a mark name that was never created?It throws a `SyntaxError` — the API refuses to resolve a name that is not in the timeline. That matters in code paths that mark conditionally: if the start mark is behind a feature flag or an early return, the measure call at the end blows up in production. Either guard with `performance.getEntriesByName(startMark).length`, or wrap the instrumentation so a failed measure can never break the feature it is measuring.
- Your SPA marks a phase on every route change and never clears. What goes wrong?The performance timeline accumulates entries for the lifetime of the tab. Each `getEntriesByType('measure')` call then returns an ever-growing array, reporting code that scans it gets slower, and any `detail` objects you attached stay reachable, so they are never collected. Clear with `performance.clearMarks(name)` and `performance.clearMeasures(name)` right after you report the phase.
saying these in an interview costs you the question
- Thinks Date.now() differences are equivalent to a measure
- Believes marks are automatically sent to an analytics backend
- Never clears marks in a long-lived single-page app
- Confuses the measure's name with its two mark names
- Assumes mark timestamps are Unix-epoch milliseconds