Self-Awareness: Strengths and Weaknesses Questions
How honestly and usefully a candidate assesses their own strengths, weaknesses, and growth areas. Covers naming a real weakness with a plan to address it, articulating genuine strengths without arrogance, and demonstrating calibrated self-knowledge and reflective self-assessment. Distinct from feedback reception: this is the candidate's own self-assessment, including the classic strengths/weaknesses question.
Design and implement a small theming system using CSS variables and JavaScript to support light/dark modes and a per-user custom accent color. Show CSS variable declarations and example JS to toggle the theme and persist user preference (localStorage or server). Describe accessibility considerations (contrast) and how to avoid FOUC.
Sample Answer
Approach: use CSS custom properties for tokens (background, text, accent). Keep a :root default (light), a .dark class to override, and a CSS variable --accent that can be set per-user on :root. Use a tiny JS module to read/save preference (localStorage + optional server sync), set the class and inline variable early to avoid FOUC, and expose a toggle and accent setter.
:root {
--bg: #ffffff;
--text: #111111;
--muted: #666666;
--accent: #0066ff; /* default */
--focus-ring: 3px solid rgba(0,102,255,0.25);
}
.dark {
--bg: #0b0b0f;
--text: #e6e6e6;
--muted: #9a9aa0;
/* --accent remains user-set or falls back to default */
}
body {
background: var(--bg);
color: var(--text);
transition: background .2s, color .2s;
}
/* components use accent */
.button {
background: var(--accent);
color: #fff;
border-radius: 6px;
padding: 8px 12px;
}
.button:focus {
outline: var(--focus-ring);
}
// theming.js
const THEME_KEY = 'theme'; // 'light'|'dark'|'system'
const ACCENT_KEY = 'accent'; // hex like '#ff6600'
function applyTheme(theme) {
const root = document.documentElement;
root.classList.remove('dark');
if (theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
root.classList.add('dark');
}
}
function applyAccent(hex) {
if (!/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(hex)) return;
document.documentElement.style.setProperty('--accent', hex);
}
export function initTheme() {
// run as early as possible (inline script) to avoid FOUC
const storedTheme = localStorage.getItem(THEME_KEY) || 'system';
const storedAccent = localStorage.getItem(ACCENT_KEY);
applyTheme(storedTheme);
if (storedAccent) applyAccent(storedAccent);
// watch system changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
const current = localStorage.getItem(THEME_KEY) || 'system';
if (current === 'system') applyTheme('system');
});
}
export function setTheme(theme) {
localStorage.setItem(THEME_KEY, theme);
applyTheme(theme);
// optionally sync to server:
// fetch('/api/user/theme', {method:'POST', body:JSON.stringify({theme})});
}
export function setAccent(hex) {
localStorage.setItem(ACCENT_KEY, hex);
applyAccent(hex);
// optionally sync to server
}
Accessibility considerations:
- Ensure color combinations meet WCAG contrast: normal text 4.5:1, large text 3:1. When generating/storing user accent, compute contrast of accent on bg and provide fallbacks or suggest darker/lighter variants. Use tools or algorithms (luminance + contrast ratio).
- Provide visible focus styles (not removed), high-contrast theme option, and respect prefers-reduced-motion.
Avoiding FOUC:
- Run initTheme in an inline <script> in the document head before CSS loads or as the very first script so CSS variables/class are set before render.
- Example inline: <script>/* small init: read localStorage, set documentElement.classList and --accent */</script>
This prevents flicker when CSS loads.
Edge cases:
- Invalid accent input, handle and sanitize; provide presets.
- If server sync fails, fallback to localStorage.
- Consider SSR: render correct class on server using user's stored preference.
Explain how you would set up visual regression testing for a component library. Which tools would you choose (e.g., Storybook+Chromatic, Percy, Playwright snapshots), how do you manage flaky snapshots or dynamic content, and how do you integrate visual checks into CI without blocking developer velocity?
Sample Answer
Start by mapping goals: catch unintended visual regressions in a component library while keeping fast feedback for devs. My approach:
Tools
- Author components in Storybook (single source of truth).
- For automated visual diffs choose Chromatic or Percy for hosted, or Playwright’s snapshot + Pixelmatch for self-hosted. I prefer Storybook + Chromatic for component-centric teams (tight Storybook integration, PR gatings, review UI); use Playwright snapshots when you need integration/flow tests or self-hosting.
Test design
- Create focused stories per visual state (variants, breakpoints, RTL, high-contrast).
- Keep stories deterministic: inject fixed data, mock network, fixed fonts and viewport, and set consistent fonts/OS rendering via CSS resets.
Managing flaky/dynamic content
- Strip or mask dynamic regions before snapshot (timestamps, random IDs) using CSS classes or Chromatic/Percy masking rules.
- Use deterministic test data and seed RNGs.
- For content that must vary, render controlled fixtures (e.g., feature flags off).
- If diffs happen, triage with a “review queue”: automated heuristics (diff % threshold) plus human review to accept/reject.
CI integration without blocking velocity
- Run lightweight visual checks (critical components, diff thresholds, small viewport matrix) as part of pre-merge CI to surface obvious regressions quickly.
- Run full visual suite asynchronously after merge or on a nightly job; report failures to a dashboard or Slack channel and create tickets automatically when thresholds exceeded.
- Use Chromatic/Percy’s PR integration to show diffs inline but don’t block merges by default; make blocking optional for high-risk core components.
- Cache Storybook builds and parallelize snapshots to reduce runtime.
Operational practices
- Define ownership and SLA for triaging visual diffs.
- Keep snapshot baselines tidy: review and update baselines in the same PR that makes intentional visual changes.
- Monitor false-positive rate and tune thresholds/masks.
This setup balances confidence in UI stability with developer speed: fast, focused checks on PRs and deeper, automated sweeps outside the critical path.
Implement an accessible modal dialog component in React using TypeScript. Requirements: traps focus inside the dialog while open, returns focus to the opener on close, closes on Escape, sets appropriate ARIA attributes (role='dialog', aria-modal='true'), and prevents background scrolling. Provide the component API (props) and describe edge cases like nested modals and server-side rendering.
Sample Answer
Approach: create a Modal component that renders children in a portal, sets role/aria-modal, traps focus with keyboard handling, saves/restores opener focus, closes on Escape, and toggles document.body overflow. Provide a lightweight focus-trap (Tab cycling) and a modal stack counter to support nested modals and SSR guards.
API (props):
- isOpen: boolean
- onClose: () => void
- initialFocusRef?: React.RefObject<HTMLElement>
- ariaLabel?: string
- children: React.ReactNode
- className?: string
Implementation:
import React, { useEffect, useRef } from "react";
import { createPortal } from "react-dom";
type Props = {
isOpen: boolean;
onClose: () => void;
initialFocusRef?: React.RefObject<HTMLElement>;
ariaLabel?: string;
children: React.ReactNode;
className?: string;
};
let openModalCount = 0;
export default function Modal({ isOpen, onClose, initialFocusRef, ariaLabel, children, className }: Props) {
const containerRef = useRef<HTMLDivElement | null>(null);
const previouslyFocused = useRef<HTMLElement | null>(null);
useEffect(() => {
if (typeof document === "undefined") return;
if (!containerRef.current) {
containerRef.current = document.createElement("div");
}
const container = containerRef.current;
document.body.appendChild(container);
return () => {
document.body.contains(container) && document.body.removeChild(container);
};
}, []);
useEffect(() => {
if (!isOpen) return;
if (typeof document === "undefined") return;
openModalCount++;
previouslyFocused.current = document.activeElement as HTMLElement | null;
// prevent background scroll only when first modal opens
if (openModalCount === 1) document.body.style.overflow = "hidden";
const focusTarget = initialFocusRef?.current ?? (() => {
const btn = document.querySelector<HTMLElement>("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])");
return btn ?? null;
})();
focusTarget?.focus();
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
e.stopPropagation();
onClose();
} else if (e.key === "Tab") {
// basic focus trap
const focusable = Array.from(container.querySelectorAll<HTMLElement>("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])"))
.filter(el => !el.hasAttribute("disabled"));
if (focusable.length === 0) {
e.preventDefault();
return;
}
const idx = focusable.indexOf(document.activeElement as HTMLElement);
if (e.shiftKey && idx === 0) {
e.preventDefault();
focusable[focusable.length - 1].focus();
} else if (!e.shiftKey && idx === focusable.length - 1) {
e.preventDefault();
focusable[0].focus();
}
}
};
document.addEventListener("keydown", onKeyDown, true);
return () => {
document.removeEventListener("keydown", onKeyDown, true);
openModalCount--;
if (openModalCount === 0) document.body.style.overflow = "";
// restore focus only if opener still exists
previouslyFocused.current?.focus();
};
}, [isOpen, onClose, initialFocusRef]);
if (!isOpen || typeof document === "undefined" || !containerRef.current) return null;
const modal = (
<div
role="dialog"
aria-modal="true"
aria-label={ariaLabel}
className={className}
style={{ position: "fixed", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000 }}
onMouseDown={(e) => {
// close when clicking overlay (but not if clicking inside dialog)
if (e.target === e.currentTarget) onClose();
}}
>
<div style={{ background: "white", padding: 20, minWidth: 300 }}>{children}</div>
</div>
);
return createPortal(modal, containerRef.current);
}
Key points:
- Focus trap: intercept Tab/Shift+Tab and cycle focusable elements inside modal.
- Restore focus to opener saved at open time.
- Escape key closes modal.
- Prevent body scroll using a global counter to support nested modals (only clear overflow when last modal closes).
- Use portal so modal is outside document flow.
Edge cases:
- Nested modals: use openModalCount so inner modals don't re-enable scroll; ensure only topmost handles Escape/closing if desired.
- Server-side rendering: guard for document/window; render null server-side.
- No focusable elements: keep focus on container or provide initialFocusRef.
- Dynamic content: recompute focusable elements on Tab; heavy focus-trap needs mutation observers for complex UIs.
- Accessibility: provide aria-label or aria-labelledby, ensure dialog content has semantic headings and announcements for screen readers.
A data-table component renders thousands of rows in the DOM and performs poorly. Propose and compare strategies to optimize rendering and the UX: virtualization (windowing), pagination, server-side paging, incremental rendering, and progressive hydration. Explain when each strategy is appropriate and the UX trade-offs involved.
Sample Answer
Situation: A table rendering thousands of rows is slow because the browser must create and layout many DOM nodes, update JS state, and paint — causing jank and high memory.
Solution overview — strategies, when to use them, and UX trade-offs:
- Virtualization (windowing)
- What: Render only visible rows + small buffer; reuse DOM nodes as user scrolls.
- When: Large datasets where users expect continuous scroll and low latency on client.
- UX trade-offs: Smooth scrolling and instant local navigation; loses native scroll-to-position accuracy unless implemented; complexity for variable-height rows and keyboard/selection handling.
- Example libraries: react-window, react-virtualized.
- Pagination (client-side)
- What: Split data into pages and render one page at a time in the client.
- When: Dataset moderate in size, user tasks are page-oriented (review, compare small subsets).
- UX trade-offs: Clear navigation and lower DOM cost; extra clicks to see more, less exploratory fluidity.
- Server-side paging
- What: Fetch paged data from server on demand (offset/limit or keyset).
- When: Very large datasets, security/permission filters, or to reduce client memory and initial load.
- UX trade-offs: Consistent performance, reduced client memory; introduces network latency, requires loading indicators and caching; keyset pagination preferred for performance/consistency.
- Incremental rendering (chunking / requestIdleCallback)
- What: Render table in smaller batches or defer non-critical rows/columns.
- When: Initial render cost is high but dataset must appear quickly; acceptable to progressively fill content.
- UX trade-offs: Faster Time-to-First-Paint, but table populates gradually — can feel flickery without placeholders.
- Progressive hydration (SSR then hydrate parts)
- What: Server-render initial markup for SEO/first paint, hydrate interactive portions progressively on the client.
- When: Need fast first meaningful paint and SEO, but client interactivity can be deferred (e.g., analytics rows, heavy cell renderers).
- UX trade-offs: Fast initial view and perceived performance; interactions unavailable until hydrated, complexity in splitting components and state synchronization.
When to choose:
- If users expect infinite scroll and instant local navigation → virtualization + optional server-side data for huge sets.
- If users work in discrete pages (reports) → pagination or server-side paging with good caching.
- If initial load is the bottleneck → incremental rendering + progressive hydration (SSR) for fast paint.
- For strongest scalability and low memory → server-side paging + client virtualization for per-page rendering.
Implementation tips:
- Use keyset pagination for large changing datasets.
- Combine strategies: SSR the first page, client-virtualize visible area, lazy-load additional pages.
- Provide UX polish: placeholders, loading skeletons, row-selection persistence, and accessible keyboard focus management.
- Measure: profile paint/layout, memory, TTFMP, and perceived latency; pick the lowest-friction combination.
Compare CSS-in-JS, CSS Modules, and utility-first frameworks (e.g., Tailwind) for building and maintaining a large-scale component library. Discuss trade-offs in performance, developer experience, theming capabilities, runtime costs, and server-side rendering implications.
Sample Answer
Situation: You're choosing a styling strategy for a large component library — you need maintainability, performance, theming, good DX, low runtime cost, and SSR compatibility.
Summary comparison (quick):
- CSS-in-JS (styled-components, Emotion)
- CSS Modules
- Utility-first (Tailwind)
Performance
- CSS-in-JS: Generates styles at runtime (unless using babel extraction). Good critical-css but can add JS runtime cost and style recalculation on mount. Can cause larger bundle if library ships many styled definitions.
- CSS Modules: Static CSS files → minimal runtime overhead, browser handles CSSOM. Best raw runtime performance.
- Tailwind: Generates atomic classes at build-time; runtime cost is just applying classes. Very fast in browser; CSS size can be large without purging.
Developer experience
- CSS-in-JS: Excellent co-location, dynamic props, and component-scoped styling. Strong DX for component authors; tooling matured but sometimes harder to extract global design tokens.
- CSS Modules: Familiar semantics, predictable scoping, easy to reason about. Less ergonomic for highly dynamic styles.
- Tailwind: Fast to prototype, consistent spacing system, steep mental shift to utility thinking. Verbose class lists unless using composition utilities (clsx, @apply).
Theming capabilities
- CSS-in-JS: Excellent — theme objects injected via provider, runtime theme switching easy, per-component overrides straightforward.
- CSS Modules: Theming typically via CSS variables or build-time token substitution; dynamic theme switching requires CSS vars anyway.
- Tailwind: Theming via config + CSS variables. Tailwind’s design tokens are build-time; dynamic runtime themes need CSS variables or toggling class on root.
Runtime costs & bundle
- CSS-in-JS: Runtime style generation can increase JS work and bundle if not extracted. Libraries may add ~kBs.
- CSS Modules: CSS shipped as separate files; small JS overhead. Easier to CDN/cache.
- Tailwind: Large generated CSS unless purged; but purge and JIT mode yield small bundles. No runtime JS cost for styling.
Server-side rendering (SSR)
- CSS-in-JS: Good SSR support (extract critical CSS to HTML). Needs framework integration to avoid FOUC.
- CSS Modules: SSR trivial—CSS files are linked; no extraction needed.
- Tailwind: SSR works fine; ensure purge/JIT includes templates used at runtime to avoid missing classes. FOUC risk low if CSS delivered in head.
Trade-offs & recommendation
- If you need dynamic theming, encapsulation, and author ergonomics for a component library: CSS-in-JS (or extracted/compiled variant) is strong.
- If you prioritize raw runtime performance, simplicity, and predictable bundling: CSS Modules.
- If you want a consistent design system with high dev velocity and small runtime cost: Tailwind (use JIT + CSS vars for theming).
- Hybrid approach: Use Tailwind or global tokens for layout/utility consistency and CSS Modules or CSS-in-JS for component-specific complex styles and dynamic theming.
Edge considerations
- Accessibility, critical CSS, style collisions, long-term maintainability, and team familiarity should drive final choice.
Unlock Full Question Bank
Get access to all 42 Self-Awareness: Strengths and Weaknesses interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.