Frontend Interview Questions - Medium

Medium-level frontend interview questions covering advanced React, Vue, state management, and performance optimization.

Q1: Explain React Context API and when to use it.

Answer:

Prop Drilling Problem

Context Solution

 1import { createContext, useContext, useState } from 'react';
 2
 3// Create context
 4const UserContext = createContext();
 5
 6// Provider component
 7function App() {
 8  const [user, setUser] = useState({ name: 'Alice', role: 'admin' });
 9  
10  return (
11    <UserContext.Provider value={{ user, setUser }}>
12      <Header />
13      <Main />
14    </UserContext.Provider>
15  );
16}
17
18// Consumer component (deep in tree)
19function UserProfile() {
20  const { user, setUser } = useContext(UserContext);
21  
22  return (
23    <div>
24      <h1>{user.name}</h1>
25      <p>Role: {user.role}</p>
26      <button onClick={() => setUser({ ...user, name: 'Bob' })}>
27        Change Name
28      </button>
29    </div>
30  );
31}

Context Flow

When to Use:

  • Theme (dark/light mode)
  • User authentication
  • Language/localization
  • Global settings

When NOT to Use:

  • Frequently changing data (causes re-renders)
  • Complex state logic (use Redux/Zustand)

Q2: What is React useCallback and useMemo?

Answer:

useMemo

 1import { useState, useMemo } from 'react';
 2
 3function ExpensiveComponent({ items }) {
 4  const [count, setCount] = useState(0);
 5  
 6  // Without useMemo: Recalculates on every render
 7  // const total = items.reduce((sum, item) => sum + item.price, 0);
 8  
 9  // With useMemo: Only recalculates when items change
10  const total = useMemo(() => {
11    console.log('Calculating total...');
12    return items.reduce((sum, item) => sum + item.price, 0);
13  }, [items]); // Dependency array
14  
15  return (
16    <div>
17      <p>Total: ${total}</p>
18      <button onClick={() => setCount(count + 1)}>
19        Count: {count}
20      </button>
21    </div>
22  );
23}

useCallback

 1import { useState, useCallback } from 'react';
 2import { memo } from 'react';
 3
 4// Child component (memoized)
 5const Button = memo(({ onClick, children }) => {
 6  console.log('Button rendered');
 7  return <button onClick={onClick}>{children}</button>;
 8});
 9
10function Parent() {
11  const [count, setCount] = useState(0);
12  const [other, setOther] = useState(0);
13  
14  // Without useCallback: New function on every render
15  // const increment = () => setCount(count + 1);
16  
17  // With useCallback: Same function reference
18  const increment = useCallback(() => {
19    setCount(c => c + 1);
20  }, []); // Empty deps: function never changes
21  
22  return (
23    <div>
24      <p>Count: {count}</p>
25      <Button onClick={increment}>Increment</Button>
26      <button onClick={() => setOther(other + 1)}>
27        Other: {other}
28      </button>
29    </div>
30  );
31}

When to Use


Q3: Explain Vue Vuex state management.

Answer:

Vuex Architecture

Vuex Store

 1import { createStore } from 'vuex';
 2
 3const store = createStore({
 4  // State
 5  state() {
 6    return {
 7      count: 0,
 8      user: null,
 9      items: []
10    }
11  },
12  
13  // Getters (like computed properties)
14  getters: {
15    doubleCount(state) {
16      return state.count * 2;
17    },
18    itemCount(state) {
19      return state.items.length;
20    }
21  },
22  
23  // Mutations (synchronous)
24  mutations: {
25    increment(state) {
26      state.count++;
27    },
28    setUser(state, user) {
29      state.user = user;
30    },
31    addItem(state, item) {
32      state.items.push(item);
33    }
34  },
35  
36  // Actions (asynchronous)
37  actions: {
38    async fetchUser({ commit }, userId) {
39      const response = await fetch(`/api/users/${userId}`);
40      const user = await response.json();
41      commit('setUser', user);
42    },
43    
44    incrementAsync({ commit }) {
45      setTimeout(() => {
46        commit('increment');
47      }, 1000);
48    }
49  }
50});

Using in Components

 1<template>
 2  <div>
 3    <p>Count: {{ count }}</p>
 4    <p>Double: {{ doubleCount }}</p>
 5    <button @click="increment">Increment</button>
 6    <button @click="incrementAsync">Increment Async</button>
 7  </div>
 8</template>
 9
10<script>
11import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
12
13export default {
14  computed: {
15    ...mapState(['count']),
16    ...mapGetters(['doubleCount'])
17  },
18  methods: {
19    ...mapMutations(['increment']),
20    ...mapActions(['incrementAsync'])
21  }
22}
23</script>

Q4: What is React useReducer and when to use it?

Answer:

Reducer Pattern

useReducer Example

 1import { useReducer } from 'react';
 2
 3// Reducer function
 4function reducer(state, action) {
 5  switch (action.type) {
 6    case 'increment':
 7      return { count: state.count + 1 };
 8    case 'decrement':
 9      return { count: state.count - 1 };
10    case 'reset':
11      return { count: 0 };
12    case 'set':
13      return { count: action.payload };
14    default:
15      throw new Error(`Unknown action: ${action.type}`);
16  }
17}
18
19function Counter() {
20  const [state, dispatch] = useReducer(reducer, { count: 0 });
21  
22  return (
23    <div>
24      <p>Count: {state.count}</p>
25      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
26      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
27      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
28      <button onClick={() => dispatch({ type: 'set', payload: 10 })}>
29        Set to 10
30      </button>
31    </div>
32  );
33}

Complex State Example

 1function todoReducer(state, action) {
 2  switch (action.type) {
 3    case 'add':
 4      return {
 5        ...state,
 6        todos: [...state.todos, { id: Date.now(), text: action.text, done: false }]
 7      };
 8    case 'toggle':
 9      return {
10        ...state,
11        todos: state.todos.map(todo =>
12          todo.id === action.id ? { ...todo, done: !todo.done } : todo
13        )
14      };
15    case 'delete':
16      return {
17        ...state,
18        todos: state.todos.filter(todo => todo.id !== action.id)
19      };
20    case 'setFilter':
21      return { ...state, filter: action.filter };
22    default:
23      return state;
24  }
25}
26
27function TodoApp() {
28  const [state, dispatch] = useReducer(todoReducer, {
29    todos: [],
30    filter: 'all'
31  });
32  
33  // ... component implementation
34}

useState vs useReducer:


Q5: Explain Vue composables (reusable composition functions).

Answer:

Creating a Composable

 1// useCounter.js
 2import { ref, computed } from 'vue';
 3
 4export function useCounter(initialValue = 0) {
 5  const count = ref(initialValue);
 6  const doubled = computed(() => count.value * 2);
 7  
 8  function increment() {
 9    count.value++;
10  }
11  
12  function decrement() {
13    count.value--;
14  }
15  
16  function reset() {
17    count.value = initialValue;
18  }
19  
20  return {
21    count,
22    doubled,
23    increment,
24    decrement,
25    reset
26  };
27}

Using Composables

 1<script setup>
 2import { useCounter } from './useCounter';
 3
 4const { count, doubled, increment, decrement, reset } = useCounter(10);
 5</script>
 6
 7<template>
 8  <div>
 9    <p>Count: {{ count }}</p>
10    <p>Doubled: {{ doubled }}</p>
11    <button @click="increment">+</button>
12    <button @click="decrement">-</button>
13    <button @click="reset">Reset</button>
14  </div>
15</template>

Advanced Composable

 1// useFetch.js
 2import { ref, watchEffect } from 'vue';
 3
 4export function useFetch(url) {
 5  const data = ref(null);
 6  const error = ref(null);
 7  const loading = ref(false);
 8  
 9  async function fetchData() {
10    loading.value = true;
11    error.value = null;
12    
13    try {
14      const response = await fetch(url.value);
15      data.value = await response.json();
16    } catch (e) {
17      error.value = e;
18    } finally {
19      loading.value = false;
20    }
21  }
22  
23  // Re-fetch when URL changes
24  watchEffect(() => {
25    fetchData();
26  });
27  
28  return { data, error, loading, refetch: fetchData };
29}
 1<script setup>
 2import { ref } from 'vue';
 3import { useFetch } from './useFetch';
 4
 5const userId = ref(1);
 6const url = computed(() => `/api/users/${userId.value}`);
 7const { data, error, loading, refetch } = useFetch(url);
 8</script>
 9
10<template>
11  <div>
12    <div v-if="loading">Loading...</div>
13    <div v-else-if="error">Error: {{ error.message }}</div>
14    <div v-else-if="data">
15      <h1>{{ data.name }}</h1>
16      <p>{{ data.email }}</p>
17    </div>
18    <button @click="refetch">Refresh</button>
19  </div>
20</template>

Q6: What is React reconciliation and the key prop?

Answer:

Without Keys

With Keys

Key Prop Usage

 1// ❌ Bad: Using index as key
 2function BadList({ items }) {
 3  return (
 4    <ul>
 5      {items.map((item, index) => (
 6        <li key={index}>{item.name}</li>
 7      ))}
 8    </ul>
 9  );
10}
11
12// ✅ Good: Using unique ID as key
13function GoodList({ items }) {
14  return (
15    <ul>
16      {items.map(item => (
17        <li key={item.id}>{item.name}</li>
18      ))}
19    </ul>
20  );
21}

Problems with Index as Key:

 1// Initial: [A, B, C]
 2<li key={0}>A <input /></li>
 3<li key={1}>B <input /></li>
 4<li key={2}>C <input /></li>
 5
 6// After deleting A: [B, C]
 7<li key={0}>B <input /></li>  // Was key={1}, now key={0}
 8<li key={1}>C <input /></li>  // Was key={2}, now key={1}
 9
10// Input states get mixed up!

Q7: Explain CSS-in-JS and styled-components.

Answer:

styled-components

 1import styled from 'styled-components';
 2
 3// Create styled component
 4const Button = styled.button`
 5  background: ${props => props.primary ? 'blue' : 'gray'};
 6  color: white;
 7  padding: 10px 20px;
 8  border: none;
 9  border-radius: 4px;
10  cursor: pointer;
11  
12  &:hover {
13    opacity: 0.8;
14  }
15  
16  &:disabled {
17    opacity: 0.5;
18    cursor: not-allowed;
19  }
20`;
21
22const Container = styled.div`
23  max-width: 1200px;
24  margin: 0 auto;
25  padding: 20px;
26`;
27
28// Usage
29function App() {
30  return (
31    <Container>
32      <Button primary>Primary Button</Button>
33      <Button>Secondary Button</Button>
34      <Button disabled>Disabled Button</Button>
35    </Container>
36  );
37}

Dynamic Styling

 1const Card = styled.div`
 2  padding: 20px;
 3  background: ${props => props.theme.background};
 4  color: ${props => props.theme.text};
 5  border: 1px solid ${props => props.theme.border};
 6  border-radius: 8px;
 7  
 8  ${props => props.elevated && `
 9    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
10  `}
11  
12  ${props => props.size === 'large' && `
13    padding: 40px;
14    font-size: 1.2rem;
15  `}
16`;
17
18// Usage
19<Card elevated size="large">
20  Content
21</Card>

Theming

 1import { ThemeProvider } from 'styled-components';
 2
 3const lightTheme = {
 4  background: '#ffffff',
 5  text: '#000000',
 6  border: '#cccccc'
 7};
 8
 9const darkTheme = {
10  background: '#1a1a1a',
11  text: '#ffffff',
12  border: '#444444'
13};
14
15function App() {
16  const [isDark, setIsDark] = useState(false);
17  
18  return (
19    <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
20      <Card>Themed content</Card>
21      <button onClick={() => setIsDark(!isDark)}>
22        Toggle Theme
23      </button>
24    </ThemeProvider>
25  );
26}

Q8: What are React Portals?

Answer:

Portal Usage

 1import { createPortal } from 'react-dom';
 2
 3function Modal({ isOpen, onClose, children }) {
 4  if (!isOpen) return null;
 5  
 6  // Render into document.body instead of parent
 7  return createPortal(
 8    <div className="modal-overlay" onClick={onClose}>
 9      <div className="modal-content" onClick={e => e.stopPropagation()}>
10        {children}
11        <button onClick={onClose}>Close</button>
12      </div>
13    </div>,
14    document.body // Target container
15  );
16}
17
18function App() {
19  const [isOpen, setIsOpen] = useState(false);
20  
21  return (
22    <div style={{ overflow: 'hidden', position: 'relative' }}>
23      <h1>My App</h1>
24      <button onClick={() => setIsOpen(true)}>Open Modal</button>
25      
26      <Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
27        <h2>Modal Content</h2>
28        <p>This renders outside the parent DOM hierarchy</p>
29      </Modal>
30    </div>
31  );
32}

DOM Structure

Why Use Portals:

  • Escape overflow: hidden or z-index constraints
  • Render modals at document root
  • Tooltips that extend beyond parent
  • Dropdowns that need to overlay everything

Q9: Explain Vue watchers vs computed properties.

Answer:

Computed Properties

 1<script setup>
 2import { ref, computed } from 'vue';
 3
 4const firstName = ref('John');
 5const lastName = ref('Doe');
 6
 7// Computed: Cached, only recalculates when dependencies change
 8const fullName = computed(() => {
 9  console.log('Computing full name');
10  return `${firstName.value} ${lastName.value}`;
11});
12
13// Computed with getter and setter
14const fullNameEditable = computed({
15  get() {
16    return `${firstName.value} ${lastName.value}`;
17  },
18  set(value) {
19    const parts = value.split(' ');
20    firstName.value = parts[0];
21    lastName.value = parts[1];
22  }
23});
24</script>
25
26<template>
27  <div>
28    <input v-model="firstName" />
29    <input v-model="lastName" />
30    <p>Full name: {{ fullName }}</p>
31    <input v-model="fullNameEditable" />
32  </div>
33</template>

Watchers

 1<script setup>
 2import { ref, watch, watchEffect } from 'vue';
 3
 4const searchQuery = ref('');
 5const results = ref([]);
 6const loading = ref(false);
 7
 8// Watch: React to changes, perform side effects
 9watch(searchQuery, async (newQuery, oldQuery) => {
10  console.log(`Query changed from "${oldQuery}" to "${newQuery}"`);
11  
12  if (newQuery.length < 3) {
13    results.value = [];
14    return;
15  }
16  
17  loading.value = true;
18  try {
19    const response = await fetch(`/api/search?q=${newQuery}`);
20    results.value = await response.json();
21  } finally {
22    loading.value = false;
23  }
24}, {
25  immediate: false,  // Don't run on mount
26  deep: false        // Don't watch nested properties
27});
28
29// watchEffect: Automatically tracks dependencies
30watchEffect(() => {
31  console.log(`Search query is: ${searchQuery.value}`);
32  // Automatically re-runs when searchQuery changes
33});
34</script>

When to Use Each


Q10: What is code splitting and lazy loading in React?

Answer:

React.lazy and Suspense

 1import { lazy, Suspense } from 'react';
 2
 3// Lazy load components
 4const Dashboard = lazy(() => import('./Dashboard'));
 5const Profile = lazy(() => import('./Profile'));
 6const Settings = lazy(() => import('./Settings'));
 7
 8function App() {
 9  const [page, setPage] = useState('dashboard');
10  
11  return (
12    <div>
13      <nav>
14        <button onClick={() => setPage('dashboard')}>Dashboard</button>
15        <button onClick={() => setPage('profile')}>Profile</button>
16        <button onClick={() => setPage('settings')}>Settings</button>
17      </nav>
18      
19      <Suspense fallback={<div>Loading...</div>}>
20        {page === 'dashboard' && <Dashboard />}
21        {page === 'profile' && <Profile />}
22        {page === 'settings' && <Settings />}
23      </Suspense>
24    </div>
25  );
26}

Route-based Code Splitting

 1import { BrowserRouter, Routes, Route } from 'react-router-dom';
 2import { lazy, Suspense } from 'react';
 3
 4const Home = lazy(() => import('./pages/Home'));
 5const About = lazy(() => import('./pages/About'));
 6const Contact = lazy(() => import('./pages/Contact'));
 7
 8function App() {
 9  return (
10    <BrowserRouter>
11      <Suspense fallback={<div>Loading page...</div>}>
12        <Routes>
13          <Route path="/" element={<Home />} />
14          <Route path="/about" element={<About />} />
15          <Route path="/contact" element={<Contact />} />
16        </Routes>
17      </Suspense>
18    </BrowserRouter>
19  );
20}

Loading Flow

Bundle Analysis

Benefits:

  • Faster initial page load
  • Better performance on slow networks
  • Load features only when needed
  • Improved user experience

Summary

Medium frontend topics:

  • Context API: Avoid prop drilling, global state
  • useCallback/useMemo: Performance optimization, memoization
  • Vuex: Centralized state management for Vue
  • useReducer: Complex state logic, predictable updates
  • Vue Composables: Reusable composition functions
  • Reconciliation: React's diffing algorithm, key prop importance
  • CSS-in-JS: Scoped styles, dynamic styling, theming
  • React Portals: Render outside parent DOM hierarchy
  • Watchers vs Computed: Side effects vs derived values
  • Code Splitting: Lazy loading, route-based splitting

These concepts enable building scalable, performant frontend applications.

Related Snippets