Interview Experience

Frontend Engineer Loop at a Design-Centric Company

A 5 round frontend loop at a design-centric Series C SaaS. The CSS round, the rendering round, the design-system round, and what each one was actually grading.

Frontend Engineer Loop at a Design-Centric Company

A 5 round frontend loop at a design-centric Series C SaaS. The CSS round, the rendering round, the design-system round, and what each one was actually grading.

frontend
css-flexbox
react
performance
interview-prep
theokone

By @theokone

February 10, 2026

·

Updated May 18, 2026

166 views

2

4.3 (14)

Most frontend loops I had been through before this one were generic SWE loops with one polite question about React thrown in. This was different. The company was a Series C SaaS with about 180 engineers, headquartered in San Francisco, well-known publicly for the visual quality of its product. They had a separate frontend interview track for senior frontend hires. I went through it for a Senior Frontend role and the calibration on every round was role-specific in a way I had not seen at a generic SWE loop. I want to write down each round, including the CSS round which was the one I had not prepared for, and what each one was actually grading.

The track

Five rounds plus a recruiter screen. All remote, scheduled across two weeks, total interviewer time about 5 hours:

  • Round 1: 30 min recruiter screen
  • Round 2: 60 min CSS / browser fundamentals round
  • Round 3: 60 min rendering and performance round
  • Round 4: 60 min design system architecture round
  • Round 5: 45 min behavioral with the engineering manager
  • Round 6: 45 min cross-functional round with a designer

Notice what is not on this list: a generic LeetCode-shaped algorithms round. I asked the recruiter about it. She said "if a senior frontend hire writes JavaScript that solves a coding problem, that is the floor; the ceiling is whether they can ship a calendar widget that does not jank when 800 events render".

The CSS round was the surprise

I had prepped React, prepped a system design round, prepped behavioral. I had skimmed CSS for an hour the night before because I was overconfident.

The interviewer shared a Codepen with a partial implementation of a horizontally-scrolling product carousel and said "finish it; the design comp is in the second tab; you have 50 minutes". The design comp had specific things: cards that snapped to start, a fade gradient on both edges that did not block click events on the partially-visible cards, and a focus ring on keyboard navigation that survived the snap.

The traps were real. I am going to write down what I got right and what I got wrong because I think this is the most useful round to share.

What I got right in the first 20 minutes was the basic layout, using scroll-snap-type: x mandatory on the container and scroll-snap-align: start on the cards. The fade I built using a sticky pseudo-element with a linear-gradient, positioned with pointer-events: none so it did not eat clicks.

What I got wrong was three things, in order:

First, I tried to use overflow: hidden on the parent of the focus ring container, which clipped the ring. The interviewer let me run into it before nudging me toward overflow-clip-margin (a property I had genuinely never used). The fix was to overflow-x: clip the parent and let the ring extend vertically.

Second, when I switched to keyboard navigation, the scroll-snap was fighting scrollIntoView and the focus would land on a card that was visually centered but not at the snap point. I had to read the spec live to figure out that you can pass { block: 'nearest', inline: 'start' } and then let the snap container do the rest.

Third, the gradient was layered above the cards on the leading edge but I had used mix-blend-mode: multiply to keep card colors visible. On the trailing edge that mode looked wrong because the background underneath was different. I had to switch to two different gradient layers with different blend modes per side, and the interviewer asked me to talk through why. I did, slowly.

The working CSS for the carousel, distilled:

.carousel {
  display: flex;
  overflow-x: clip;
  overflow-clip-margin: 24px;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 24px;
}

.carousel__card {
  flex: 0 0 auto;
  scroll-snap-align: start;
}

.carousel__fade--leading {
  position: sticky;
  inset-inline-start: 0;
  pointer-events: none;
  background: linear-gradient(to right, var(--bg) 0%, transparent 100%);
  mix-blend-mode: normal;
}

.carousel__fade--trailing {
  position: sticky;
  inset-inline-end: 0;
  pointer-events: none;
  background: linear-gradient(to left, var(--bg) 0%, transparent 100%);
  mix-blend-mode: multiply;
}

The keyboard handler that survived snap was:

function focusCardAt(index) {
  const card = listRef.current.children[index];
  card.scrollIntoView({ block: "nearest", inline: "start" });
  card.focus({ preventScroll: true });
}

The round was grading whether I knew CSS as a tool, not as a checklist. The traps were ones that someone who has built a real component library has hit; someone who has only consumed Tailwind classes would not have hit them. I was glad I had spent two years owning a design system at my previous job; I would have been completely lost otherwise.

The rendering and performance round

The prompt: a list virtualization round in React. The interviewer pasted a <List> component that rendered 50,000 rows synchronously and said "make this work; you can use any library or write your own". I picked react-window because that was a defensible production choice, and used the round's last 20 minutes to talk through what react-window was doing under the hood (a windowed render with a fixed-height row, a scroll listener computing the visible range, and a wrapper that absolute-positioned each rendered row).

The interviewer then asked four follow-ups: variable row height (you switch to react-virtuoso or do measurement on render), keyboard accessibility (you have to manage focus across rows that mount and unmount, which is genuinely hard), screen-reader announcements (live regions plus a careful aria-rowcount), and finally, "what changes if the rows themselves are images that lazy-load?" That last question was the round. The answer is that lazy-loaded images cause layout shift inside the windowed area, which fights the virtualization, which means you need to reserve space (via aspect-ratio or width/height on the img) before the image loads.

The round was grading whether I knew the layered system that produces a smooth scroll: render path, layout, paint, and the user-perceived interaction. I had thought it was going to be a virtualization round; it turned out to be a how-the-browser-actually-works round.

The design system architecture round

60 minutes with a senior frontend engineer who maintained their design system. He drew the current state of their <Button> component on a whiteboard (a single component, 7 variants, 3 sizes, ~14 props) and said "this is becoming hard to maintain; redesign it".

I walked through the standard sequence: identify the variants that are real customer-facing variants (primary, secondary, ghost) versus variants that are component-library bleed-through (loading, disabled, icon-only); split the API into stable primitives plus opinionated composed components; consider whether to use a polymorphic as prop or named exports; think about server components and bundle size implications.

The interesting part was when I proposed using a slot-based composition (a <Button.Icon> subcomponent rather than an iconStart prop). He pushed back: their accessibility lead had banned slot composition for icon-bearing components because consumers were getting the aria labeling wrong on the slotted icon. I had to defend my choice or change my mind. I changed my mind: I proposed a typed iconStart prop that accepted a specific token-based Icon element, plus a runtime check in development that warned if the icon was missing aria attributes.

The round was grading whether I could hold a design opinion under pressure from a teammate who knew the codebase better than I did, and whether I would update on real evidence rather than defending my first answer.

The behavioral round and the Figma critique

The behavioral round was standard. The cross-functional round was 45 minutes with a designer, who showed me three Figma files and asked which one I would push back on as an engineer and why. The right answer was the second file, where the spacing tokens were inconsistent in a way that would force the engineer to either deviate from the system or rebuild a token. I picked the right file but I gave a generic engineering reason. The designer wanted me to talk about the token system itself, not the local fix. I had not thought about the round in those terms.

What the CSS round taught me about prep gaps

I got the offer. I declined for unrelated reasons (the role was NYC-onsite at a cadence I could not match). The two rounds I would prepare differently for next time: the CSS round (build three real components from scratch in a week before the loop, focusing on focus management, scroll-snap, and the parts of CSS that are actually used in production design systems), and the cross-functional round (talk in design language with the designer, not in engineering language; the round is grading whether you can collaborate, not whether you can engineer).

The whole loop was what a frontend interview should look like at a company that takes frontend seriously. It tested the actual surface area of the job, not a proxy for it. I would take that loop again even though I declined the offer.