Episode 1 — Fundamentals / 1.10 — CSS Architecture and Project Structure
Interview Questions: CSS Architecture & Project Structure
Practice questions with model answers for BEM, component-based CSS, utility vs component classes, folder structure, and CSS debugging — topics commonly asked in front-end and full-stack interviews.
How to use this material (instructions)
- Read lessons in order —
README.md, then1.10.a→1.10.e. - Practice out loud — aim for 1–2 minutes per question, then compare to the model answer.
- Structure answers — definition → example → trade-off.
- Pair with exercises —
1.10-Exercise-Questions.md. - Quick review —
1.10-Quick-Revision.md.
Beginner (Q1–Q4)
Q1. What is BEM, and why would you use it?
Why interviewers ask: Tests whether you understand CSS naming conventions and the problems they solve.
Model answer:
BEM stands for Block, Element, Modifier. It is a naming convention for CSS class names that encodes component structure directly into the name:
| Part | Syntax | Example |
|---|---|---|
| Block | .block | .card |
| Element | .block__element | .card__title |
| Modifier | .block--modifier | .card--featured |
The main benefits:
- Flat specificity — every selector is a single class (
0-1-0), so you never fight specificity wars. - Self-documenting —
.card__title--largeimmediately tells you the component, the part, and the variation. - Grepable — searching for
.cardfinds every related rule and HTML usage. - Safe to delete — if you remove the
.cardblock, you know which classes to clean up.
The trade-off is verbose class names, but the predictability pays for itself in any multi-developer project.
Q2. Explain the single-responsibility principle applied to CSS components.
Why interviewers ask: Shows you can design maintainable, composable styles.
Model answer:
A CSS component should do one thing — define the visual identity of a UI block — and not concern itself with layout context. For example:
/* Bad: card handles its own grid placement */
.card { width: 33.33%; float: left; padding: 1rem; border: 1px solid #ddd; }
/* Good: card only styles itself */
.card { padding: 1rem; border: 1px solid #ddd; border-radius: 8px; }
/* Layout is a separate concern */
.grid__col--third { width: 33.33%; }
Why this matters:
- The
.cardcan be placed in a 2-column layout, a sidebar, or a full-width page without changing its CSS. - Two developers can work on the card and the grid independently.
- Spacing, sizing, and positioning are the parent's responsibility.
Q3. What are utility classes, and how do they differ from component classes?
Why interviewers ask: Tests awareness of modern CSS strategies and architectural trade-offs.
Model answer:
| Aspect | Utility class | Component class |
|---|---|---|
| Scope | One CSS declaration | Many declarations bundled |
| Example | .text-center, .mt-4, .flex | .btn, .card, .modal |
| Where to compose | In HTML class lists | In CSS files |
| Readability | HTML is verbose, CSS is minimal | HTML is clean, CSS is large |
Utility-first (Tailwind) gives you speed and design-token consistency — every mt-4 maps to exactly 1rem, enforced by the config. The downside is long class strings in HTML.
Component-class (Bootstrap) gives you a clean, semantic HTML API (.btn.btn-primary), but the CSS file grows and dead selectors can accumulate.
Most production teams use a hybrid: component classes for repeated patterns, utility classes for one-off spacing, alignment, and colour tweaks.
Q4. How would you debug a CSS rule that isn't being applied?
Why interviewers ask: Practical problem-solving — interviewers want to see a systematic process.
Model answer:
- Inspect the element — right-click → Inspect. Open the Styles panel.
- Search for your rule — if it appears with the declaration struck through, a higher-specificity rule is winning. The winning rule is above it in the panel.
- If the rule doesn't appear at all — the selector is wrong. Check for typos, verify the class is actually on the DOM element, and make sure the stylesheet is loaded (check the Network panel).
- Check Computed — the Computed panel shows the final value. Click the arrow to jump to the rule that set it.
- Check source order — if two rules have equal specificity, the one appearing later wins.
- Check for
!important— the panel marks!importantdeclarations; they override normal cascade rules.
This systematic top-down approach finds the root cause in under a minute.
Intermediate (Q5–Q8)
Q5. Compare BEM, OOCSS, SMACSS, and ITCSS. When would you choose each?
Why interviewers ask: Tests breadth of CSS architecture knowledge.
Model answer:
| Methodology | Core principle | Best for |
|---|---|---|
| BEM | Encode structure in class names: block__element--modifier | Teams that want a single, simple naming rule |
| OOCSS | Separate structure from skin; create reusable "objects" | Highly composable design systems with many visual skins |
| SMACSS | Categorise rules: base, layout, module, state, theme | Projects that need clear rule categorisation but flexible naming |
| ITCSS | Layer CSS by specificity (inverted triangle): settings → tools → generic → elements → objects → components → utilities | Large multi-team codebases; controls cascade by architecture |
In practice, BEM + ITCSS is a popular combination: ITCSS manages file layers and import order, BEM manages naming within each layer. For modern component-framework projects, CSS Modules or scoped styles often replace manual naming conventions entirely.
Q6. What is the 7-1 SASS pattern? Walk me through its folder structure.
Why interviewers ask: Tests knowledge of scalable CSS project organisation.
Model answer:
The 7-1 pattern uses seven folders and one entry file (main.scss):
abstracts/ — Variables, mixins, functions (no CSS output)
vendors/ — Third-party styles (Normalize, library themes)
base/ — Resets, typography, global defaults
layout/ — Page structure: header, footer, grid, sidebar
components/ — Reusable UI blocks: buttons, cards, modals
pages/ — Page-specific overrides (home, checkout)
themes/ — Theme variations (dark mode, high contrast)
main.scss — Imports everything in the correct order
Import order matters: abstracts first (variables available everywhere), then vendors (your styles can override), then base → layout → components → pages → themes (increasing specificity).
Trade-off: this pattern was designed for large multi-page SASS projects. In component-framework projects (React, Vue), co-locating styles with components often makes more sense — so the 7-1 pattern shrinks to just base/ and abstracts/, while components carry their own styles.
Q7. A page has an unexpected horizontal scrollbar. Walk me through how you would find the cause.
Why interviewers ask: Real-world debugging skill — shows systematic thinking.
Model answer:
-
Quick visual scan — apply
* { outline: 1px solid red; }via DevTools on the<html>element. Every box becomes visible; the one extending beyond the viewport edge stands out. -
Binary search — if outlining everything is overwhelming, use
overflow: hiddenon<body>first to confirm the overflow is on the body scroll. Then applyoverflow: hiddento major sections one by one until the scrollbar disappears — the last section you hid contains the culprit. -
Check common causes:
- A
width: 100vwelement on a page with a vertical scrollbar (100vw includes scrollbar width → usewidth: 100%instead). - Negative margins pulling content outside the container.
- A
preorcodeblock with long unbreakable lines. - Absolutely positioned elements extending beyond the viewport.
- A
-
Fix: add
overflow-x: hiddenonly as a last resort — prefer fixing the element that overflows.
I use outline instead of border because outline does not affect the box model, so it won't introduce new layout shifts while debugging.
Q8. Should you use Tailwind CSS or write component classes? How do you decide?
Why interviewers ask: Tests architectural judgement — there's no single right answer.
Model answer:
It depends on the project's constraints:
| Factor | Tailwind (utility-first) | Component classes |
|---|---|---|
| Prototyping speed | Faster — no context-switching to CSS files | Slower — must write and name CSS |
| Design consistency | Built-in token system (spacing, color scales) | Must build your own variables |
| HTML readability | Class lists get long | Clean, semantic class attributes |
| CSS bundle size | Very small after purge | Can accumulate dead selectors |
| Shared component library | Better to extract components via framework code | Component class = clean public API |
| Team familiarity | Requires learning Tailwind vocabulary | Any CSS developer can read it |
My decision rule: for application UI (dashboards, internal tools, rapid iteration), utility-first is usually faster. For a shared component library consumed by many teams, named component classes provide a stabler public API. Most projects benefit from a hybrid approach.
Advanced (Q9–Q12)
Q9. How do CSS Modules work, and what problem do they solve?
Why interviewers ask: Tests understanding of build-tool-based CSS scoping.
Model answer:
CSS Modules transform class names at build time so they are locally scoped by default. When you write:
/* Button.module.css */
.primary { background: blue; color: white; }
The bundler (Webpack, Vite) rewrites .primary to something like .Button_primary_a3f2x. In your JavaScript you import the module:
import styles from './Button.module.css';
// styles.primary === 'Button_primary_a3f2x'
Problem solved: name collisions. Two components can both have a .primary class without conflict because their hashed names differ.
Trade-offs:
- Requires a build tool (no browser-native support).
:global()escape hatch needed for truly global styles.- Class names in HTML are dynamic (
className={styles.primary}), not static strings.
Compared to BEM (which is convention-based), CSS Modules provide tooling-enforced scoping. Both are valid — CSS Modules are common in React/Next.js projects.
Q10. Explain CSS specificity and how it relates to CSS architecture decisions.
Why interviewers ask: Specificity is the root cause of most "why isn't my style working" bugs.
Model answer:
Specificity is the weight the browser assigns to a selector to break ties in the cascade:
| Selector type | Weight | Example |
|---|---|---|
Inline style | 1-0-0-0 | style="color:red" |
| ID | 0-1-0-0 | #header |
| Class, attribute, pseudo-class | 0-0-1-0 | .card, [type="text"], :hover |
| Element, pseudo-element | 0-0-0-1 | div, ::before |
Architecture implication: if all selectors are single classes (BEM's design), every rule has the same specificity (0-0-1-0). Conflicts resolve by source order — which is predictable and easy to reason about. The moment you introduce IDs, deep nesting (.page .sidebar .widget .header a), or !important, specificity becomes unpredictable and architectural discipline collapses.
This is why every CSS methodology — BEM, OOCSS, ITCSS, utility CSS — aims to keep specificity flat or at least monotonically increasing (ITCSS layering).
Q11. How would you approach migrating a legacy CSS codebase to a modern architecture?
Why interviewers ask: Tests real-world refactoring judgement.
Model answer:
I would take an incremental approach rather than a Big Bang rewrite:
-
Audit — run a CSS stats tool (e.g.,
cssstats.com, Wallace CLI) to measure file size, selector count, specificity distribution, and duplicate declarations. -
Establish the target — pick an architecture (BEM + ITCSS for convention-based; CSS Modules for React; Tailwind for utility-first). Document the standard in a team style guide.
-
Set boundaries — introduce the new architecture for all new components. Legacy CSS stays untouched initially.
-
Extract shared tokens — move colours, spacing, and typography into CSS custom properties (or SASS variables). Both old and new CSS reference the same tokens.
-
Refactor incrementally — when touching a legacy component for a feature or bug fix, migrate its CSS to the new architecture at the same time. This amortises migration cost over normal development.
-
Lint — add Stylelint rules that enforce the new naming convention and flag anti-patterns (IDs as selectors,
!important, deep nesting). -
Track progress — periodically re-run the audit tool. Celebrate when selector count and specificity max decrease.
Key principle: never block feature work for a CSS rewrite. Migrate at the pace of regular development.
Q12. What are CSS Layers (@layer) and how do they change CSS architecture?
Why interviewers ask: Tests awareness of modern CSS features and forward-thinking architecture.
Model answer:
@layer (CSS Cascade Layers, supported in all modern browsers) lets you explicitly control cascade priority without relying on source order or specificity hacks:
@layer reset, base, components, utilities;
@layer reset {
* { margin: 0; box-sizing: border-box; }
}
@layer components {
.btn { padding: 0.5rem 1rem; border-radius: 4px; }
}
@layer utilities {
.mt-4 { margin-top: 1rem; }
}
Rules in later-declared layers always beat rules in earlier layers, regardless of specificity. This means:
- A
.mt-4utility (low specificity) in theutilitieslayer beats a.btnrule (same specificity) in thecomponentslayer — by design. - You no longer need ITCSS's inverted-triangle specificity management —
@layerdoes it natively. - Third-party CSS can be placed in a low-priority layer so your code always wins.
Architecture impact: @layer is essentially the browser-native version of what ITCSS achieved through import ordering. It makes CSS architecture more explicit and less fragile. For new projects, I would define layers that match architectural categories (reset, tokens, components, utilities, overrides).