Skip to main content

Accordion

When discussing the system design of your accordion component in an interview, focus on the following structured approach to showcase your technical depth and architectural thinking:


1. Component Architecture

  • Class-Based Design:

    • Explain why a class was chosen (encapsulation, reusability, method organization).
    • Example:
      "The class structure allows multiple independent accordion instances without state collisions. Methods like createAccordion and destroy encapsulate specific responsibilities."
  • Separation of Concerns:

    • Highlight DOM creation (createAccordionItem), event handling (addEvent), and state management (openCurrentAccordion) as distinct modules.
    • Example:
      "Separating DOM manipulation from business logic ensures maintainability and ease of debugging."

2. State Management

  • Internal State:

    • Discuss tracking currentActiveIndex to manage expanded/collapsed items.
    • Example:
      "A single source of truth for the active index simplifies state updates and avoids race conditions."
  • Stateless vs. Stateful:

    • Justify keeping state internal for simplicity vs. using external state management (e.g., Redux) for complex apps.
    • Example:
      "For a standalone component, internal state is sufficient. In a larger app, we might lift state to a global store."

3. Event Handling & Performance

  • Event Delegation:

    • Explain attaching a single listener to the container vs. individual item listeners.
    • Example:
      "Using event delegation on the parent container reduces memory overhead and handles dynamically added items."
  • Cleanup:

    • Highlight the destroy method’s role in removing listeners and preventing memory leaks.
    • Example:
      "Explicit cleanup ensures no orphaned listeners after component removal."

4. Accessibility (A11y)

  • ARIA Attributes:

    • Discuss aria-expanded, aria-hidden, and keyboard navigation.
    • Example:
      "ARIA roles make the component screen-reader friendly, while Enter/Arrow keys ensure keyboard usability."
  • Focus Management:

    • Suggest improvements like focus traps for nested accordions.

5. Performance Optimizations

  • DOM Efficiency:

    • Use of DocumentFragment for batched updates.
    • Example:
      "Batching DOM writes with fragments minimizes reflows, improving render performance."
  • CSS vs. JS Animations:

    • Propose CSS transitions for smooth expand/collapse (e.g., max-height).

6. Extensibility & Scalability

  • Dynamic Data:

    • Discuss reinitializing the accordion when data changes (e.g., createAccordion clears old items).
  • Nested Accordions:

    • Explain recursive rendering or event-bubbling adjustments.
  • Theming & Customization:

    • Suggest CSS variables for easy styling overrides.

7. Error Handling & Validation

  • Input Validation:

    • Mention constructor checks for target and data.
    • Example:
      "Validating inputs upfront prevents silent failures and improves debuggability."
  • Graceful Degradation:

    • Plan for scenarios like empty data (e.g., showing a placeholder).

8. Testing Strategy

  • Unit Tests:

    • Validate state changes (e.g., currentActiveIndex updates correctly).
    • Example:
      "Tests would simulate clicks/keyboard events and assert ARIA attribute changes."
  • Integration Tests:

    • Verify DOM updates and accessibility compliance (e.g., using Jest + Testing Library).

9. Integration with Larger Systems

  • Framework Compatibility:

    • Discuss wrapping the vanilla JS component in React/Vue (e.g., using refs and lifecycle methods).
  • API Design:

    • Expose methods like expand(index) or collapseAll() for external control.

10. Trade-Offs

  • Vanilla JS vs. Frameworks:

    • Pros: No dependencies, smaller bundle.
    • Cons: More boilerplate for complex features.
  • CSS Transitions:

    • Trade-off: Smooth animations require careful CSS/JS coordination (e.g., transitionend events).

Example Discussion Flow

  1. Start with Requirements:
    "The accordion must be reusable, accessible, and performant. Let’s break down how each part addresses these goals..."

  2. Highlight Key Decisions:
    "Using a class encapsulates logic, while event delegation ensures scalability. The destroy method prevents memory leaks."

  3. Address Edge Cases:
    "For empty data, we could add a fallback UI. Dynamic data would require a refresh method to re-render items."

  4. Future-Proofing:
    "To support animations, we’d add CSS transitions and JS hooks for transitionend. For nested accordions, we’d recursively instantiate child components."


Why This Approach Wins

  • Depth: Covers architecture, performance, accessibility, and scalability.
  • Clarity: Structured flow from requirements to implementation details.
  • Proactiveness: Anticipates edge cases and future needs.

This discussion demonstrates SDE 2/3-level system design skills by balancing practicality with foresight. 🚀

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: plum;
}
.ac-container {
max-width: 100%;
width: 500px;
/* background-color: white; */
display: flex;
flex-direction: column;
gap: 1rem;
}
.ac-container .ac-item {
border-radius: 16px;
padding: 16px;
border: 2px solid black;
}
.ac-container .ac-title {
font-size: 18px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}
.ac-container .ac-content {
display: none;
}
.ac-container .ac-content.active {
display: block;
}
</style>
</head>
<body>
<div class="ac-container"></div>
<script src="./index.js"></script>
</body>
</html>
const accordionContainer = document.querySelector(".ac-container");

const data = [
{ title: "hi 1", content: "hello" },
{ title: "hi 2", content: "hello" },
{ title: "hi 3", content: "hello" },
];

class Accordion {
constructor(data, target, onToggle) {
if (!target) throw new Error("Target element is required");
if (!Array.isArray(data)) throw new Error("Data must be an array");
this.data = data;
this.target = target;
this.currentActiveIndex = -1;
this.currentData = {};
this.handleClick = this.handleClick.bind(this);
this.handleKeyup = this.handleKeyup.bind(this);
this.onToggle = onToggle;
this.createAccordion();
this.addEvent();
}
createAccordionItem({ title, content, index }) {
const accordionItem = document.createElement("div");
const accordionTitle = document.createElement("div");
const accordionContent = document.createElement("div");

// item
accordionItem.classList.add("ac-item");
accordionItem.setAttribute("data-index", index);

// title
accordionTitle.classList.add("ac-title");
accordionTitle.innerHTML = title;
accordionTitle.setAttribute("aria-expanded", "false");
accordionTitle.setAttribute("tabindex", "0");

// content
accordionContent.classList.add("ac-content");
accordionContent.innerHTML = content;
accordionContent.setAttribute("aria-hidden", "true");

accordionItem.appendChild(accordionTitle);
accordionItem.appendChild(accordionContent);
return accordionItem;
}
createAccordion() {
this.target.innerHTML = "";
const fragment = document.createDocumentFragment();
this.data.forEach((each, index) => {
fragment.appendChild(this.createAccordionItem({ ...each, index: index }));
});
this.target?.appendChild(fragment);
}
openCurrentAccordion(currentItem) {
this.currentActiveIndex = currentItem.getAttribute("data-index");
this.currentData = this.data[this.currentActiveIndex];
const active = document.querySelector(".ac-content.active");
const currentTitle = currentItem.firstChild;
const currentContent = currentItem.lastElementChild;
if (active && active !== currentContent) {
active.classList.remove("active");
active.setAttribute("aria-hidden", "true");
active.previousElementSibling.setAttribute("aria-expanded", "false");
}
const isActive = currentContent.classList.contains("active");
currentContent.setAttribute("aria-hidden", isActive ? "true" : "false");
currentTitle.setAttribute("aria-expanded", isActive ? "false" : "true");
currentContent.classList.toggle("active");
this.onClicked(this.currentActiveIndex, this.data[this.currentActiveIndex]);
this.onToggle(
this.currentActiveIndex,
!isActive,
this.data[this.currentActiveIndex]
);
}
handleClick(e) {
if (!e.target.matches(".ac-title")) return;
this.openCurrentAccordion(e.target.parentElement);
}
handleKeyup(e) {
if (!e.target.matches(".ac-title")) return;
const keyToCheck = ["Enter", "ArrowDown", "ArrowUp"];
if (keyToCheck.includes(e.key)) {
this.openCurrentAccordion(e.target.parentElement);
}
}
addEvent() {
this.target.addEventListener("click", this.handleClick);
this.target.addEventListener("keyup", this.handleKeyup);
}
destroy() {
this.target.removeEventListener("click", this.handleClick);
this.target.removeEventListener("keyup", this.handleKeyup);
this.target.innerHTML = "";
}
onClicked(currentIndex, currentData) {
return { currentIndex, currentData };
}
}
function onToggle(index, isOpen, data) {
console.log(`Item ${index} ${data.title} is ${isOpen ? "opened" : "closed"}`);
}
new Accordion(data, accordionContainer, onToggle);