State Management in React: Context API vs Redux vs Recoil
State management is one of the most critical aspects of building scalable React applications. As your application grows in complexity, managing state across multiple components becomes increasingly challenging. This comprehensive guide explores three popular state management solutions: React's built-in Context API, Redux, and Facebook's Recoil library.
Table of Contents
1. [Introduction to State Management](#introduction) 2. [React Context API](#context-api) 3. [Redux](#redux) 4. [Recoil](#recoil) 5. [Detailed Comparison](#comparison) 6. [Real-World Project Examples](#examples) 7. [Performance Considerations](#performance) 8. [Decision Framework](#decision-framework) 9. [Migration Strategies](#migration) 10. [Conclusion](#conclusion)Introduction to State Management {#introduction}
State management in React applications involves handling data that changes over time and needs to be shared across multiple components. Without proper state management, applications can become difficult to maintain, debug, and scale.
Why State Management Matters
- Data Consistency: Ensures all components display the same data - Predictable Updates: Makes state changes traceable and debuggable - Component Communication: Enables distant components to share data - Performance: Prevents unnecessary re-renders and optimizes updates
Types of State
Before diving into solutions, it's important to understand different types of state:
1. Local State: Component-specific data (useState, useReducer) 2. Shared State: Data shared between multiple components 3. Global State: Application-wide data (user authentication, theme) 4. Server State: Data fetched from external APIs
React Context API {#context-api}
The Context API is React's built-in solution for sharing state across the component tree without prop drilling.
Architecture
`
┌─────────────────────────────────────┐
│ Context Provider │
│ ┌─────────────────────────────────┐│
│ │ Global State ││
│ │ ┌─────────────────────────┐ ││
│ │ │ Component Tree │ ││
│ │ │ ┌─────┐ ┌─────────┐ │ ││
│ │ │ │ A │ │ B │ │ ││
│ │ │ │ │ │ ┌─────┐ │ │ ││
│ │ │ └─────┘ │ │ C │ │ │ ││
│ │ │ │ └─────┘ │ │ ││
│ │ │ └─────────┘ │ ││
│ │ └─────────────────────────┘ ││
│ └─────────────────────────────────┘│
└─────────────────────────────────────┘
`
Implementation Example
`jsx
// UserContext.js
import React, { createContext, useContext, useReducer } from 'react';
const UserContext = createContext();
const initialState = { user: null, isAuthenticated: false, loading: false, error: null };
function userReducer(state, action) { switch (action.type) { case 'LOGIN_START': return { ...state, loading: true, error: null }; case 'LOGIN_SUCCESS': return { ...state, user: action.payload, isAuthenticated: true, loading: false, error: null }; case 'LOGIN_ERROR': return { ...state, loading: false, error: action.payload, isAuthenticated: false }; case 'LOGOUT': return { ...state, user: null, isAuthenticated: false, error: null }; default: return state; } }
export function UserProvider({ children }) { const [state, dispatch] = useReducer(userReducer, initialState);
const login = async (credentials) => { dispatch({ type: 'LOGIN_START' }); try { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }); const user = await response.json(); dispatch({ type: 'LOGIN_SUCCESS', payload: user }); } catch (error) { dispatch({ type: 'LOGIN_ERROR', payload: error.message }); } };
const logout = () => { dispatch({ type: 'LOGOUT' }); };
const value = { ...state, login, logout };
return (
export function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within a UserProvider');
}
return context;
}
`
`jsx
// App.js
import React from 'react';
import { UserProvider } from './UserContext';
import Dashboard from './Dashboard';
function App() {
return (
export default App;
`
`jsx
// Dashboard.js
import React from 'react';
import { useUser } from './UserContext';
function Dashboard() { const { user, isAuthenticated, login, logout, loading, error } = useUser();
const handleLogin = () => { login({ username: 'john@example.com', password: 'password' }); };
if (loading) return
return (
Welcome, {user.name}!
Please log in
export default Dashboard;
`
Pros and Cons
Pros: - Built into React (no additional dependencies) - Simple to understand and implement - Great for small to medium applications - Excellent for component composition patterns - TypeScript support out of the box
Cons: - Can cause performance issues with frequent updates - No built-in middleware support - Limited debugging tools - Can lead to provider hell with multiple contexts - Re-renders all consumers when context value changes
Redux {#redux}
Redux is a predictable state container that implements the Flux architecture pattern with a single store, actions, and reducers.
Architecture
`
┌─────────────────────────────────────────────────────────┐
│ Redux Store │
│ ┌─────────────────────────────────────────────────────┤
│ │ Global State │
│ └─────────────────────────────────────────────────────┤
│ ▲ │ │
│ │ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Reducers │ │ Selectors │ │
│ └─────────────────┘ └─────────────────┘ │
│ ▲ │ │
│ │ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Actions │ │ Components │ │
│ └─────────────────┘ └─────────────────┘ │
│ ▲ │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
`
Implementation Example
`jsx
// store/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const loginUser = createAsyncThunk( 'user/login', async (credentials, { rejectWithValue }) => { try { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }); if (!response.ok) { throw new Error('Login failed'); } return await response.json(); } catch (error) { return rejectWithValue(error.message); } } );
const userSlice = createSlice({ name: 'user', initialState: { user: null, isAuthenticated: false, loading: false, error: null }, reducers: { logout: (state) => { state.user = null; state.isAuthenticated = false; state.error = null; }, clearError: (state) => { state.error = null; } }, extraReducers: (builder) => { builder .addCase(loginUser.pending, (state) => { state.loading = true; state.error = null; }) .addCase(loginUser.fulfilled, (state, action) => { state.loading = false; state.user = action.payload; state.isAuthenticated = true; state.error = null; }) .addCase(loginUser.rejected, (state, action) => { state.loading = false; state.error = action.payload; state.isAuthenticated = false; }); } });
export const { logout, clearError } = userSlice.actions;
export default userSlice.reducer;
`
`jsx
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
export const store = configureStore({ reducer: { user: userReducer }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: ['persist/PERSIST'] } }) });
export type RootState = ReturnType`
`jsx
// hooks/redux.js
import { useDispatch, useSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from '../store';
export const useAppDispatch = () => useDispatch`
`jsx
// App.js
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store';
import Dashboard from './Dashboard';
function App() {
return (
export default App;
`
`jsx
// Dashboard.js
import React from 'react';
import { useAppSelector, useAppDispatch } from './hooks/redux';
import { loginUser, logout } from './store/userSlice';
function Dashboard() { const { user, isAuthenticated, loading, error } = useAppSelector( (state) => state.user ); const dispatch = useAppDispatch();
const handleLogin = () => { dispatch(loginUser({ username: 'john@example.com', password: 'password' })); };
const handleLogout = () => { dispatch(logout()); };
if (loading) return
return (
Welcome, {user.name}!
Please log in
export default Dashboard;
`
Pros and Cons
Pros: - Predictable state updates - Excellent debugging tools (Redux DevTools) - Large ecosystem and community - Great for complex applications - Time-travel debugging - Middleware support for side effects - Immutable state updates
Cons: - Steep learning curve - Boilerplate code (reduced with Redux Toolkit) - Can be overkill for simple applications - Additional bundle size - Requires understanding of functional programming concepts
Recoil {#recoil}
Recoil is Facebook's experimental state management library that provides a more granular approach to state management with atoms and selectors.
Architecture
`
┌─────────────────────────────────────────────────────────┐
│ Recoil Root │
│ ┌─────────────────────────────────────────────────────┤
│ │ Atom Graph │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ │ Atom A │ │ Atom B │ │ Atom C │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │ │
│ │ ▼ ▼ ▼ │
│ │ ┌─────────────────────────────────────────────┐ │
│ │ │ Selectors │ │
│ │ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ │Selector │ │Selector │ │ │
│ │ │ │ X │ │ Y │ │ │
│ │ │ └─────────┘ └─────────┘ │ │
│ │ └─────────────────────────────────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────────────────────────────┐ │
│ │ │ Components │ │
│ │ └─────────────────────────────────────────────┘ │
│ └─────────────────────────────────────────────────────┤
└─────────────────────────────────────────────────────────┘
`
Implementation Example
`jsx
// atoms/userAtoms.js
import { atom, selector } from 'recoil';
export const userState = atom({ key: 'userState', default: { user: null, isAuthenticated: false, loading: false, error: null } });
export const userProfileSelector = selector({ key: 'userProfileSelector', get: ({ get }) => { const user = get(userState); return user.user ? { displayName: user.user.name, email: user.user.email, avatar: user.user.avatar || '/default-avatar.png' } : null; } });
export const isLoadingSelector = selector({
key: 'isLoadingSelector',
get: ({ get }) => {
const user = get(userState);
return user.loading;
}
});
`
`jsx
// hooks/useAuth.js
import { useRecoilState, useRecoilValue } from 'recoil';
import { userState, userProfileSelector } from '../atoms/userAtoms';
export function useAuth() { const [user, setUser] = useRecoilState(userState); const userProfile = useRecoilValue(userProfileSelector);
const login = async (credentials) => { setUser(prev => ({ ...prev, loading: true, error: null })); try { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }); if (!response.ok) { throw new Error('Login failed'); } const userData = await response.json(); setUser({ user: userData, isAuthenticated: true, loading: false, error: null }); } catch (error) { setUser(prev => ({ ...prev, loading: false, error: error.message, isAuthenticated: false })); } };
const logout = () => { setUser({ user: null, isAuthenticated: false, loading: false, error: null }); };
return {
...user,
userProfile,
login,
logout
};
}
`
`jsx
// App.js
import React from 'react';
import { RecoilRoot } from 'recoil';
import Dashboard from './Dashboard';
function App() {
return (
export default App;
`
`jsx
// Dashboard.js
import React from 'react';
import { useAuth } from './hooks/useAuth';
function Dashboard() { const { isAuthenticated, loading, error, userProfile, login, logout } = useAuth();
const handleLogin = () => { login({ username: 'john@example.com', password: 'password' }); };
if (loading) return
return (
Welcome, {userProfile?.displayName}!
Email: {userProfile?.email}
Please log in
export default Dashboard;
`
Advanced Recoil Features
`jsx
// atoms/todoAtoms.js
import { atom, selector, selectorFamily } from 'recoil';
export const todoListState = atom({ key: 'todoListState', default: [] });
export const todoListFilterState = atom({ key: 'todoListFilterState', default: 'Show All' });
export const filteredTodoListState = selector({ key: 'filteredTodoListState', get: ({ get }) => { const filter = get(todoListFilterState); const list = get(todoListState);
switch (filter) { case 'Show Completed': return list.filter(item => item.isComplete); case 'Show Uncompleted': return list.filter(item => !item.isComplete); default: return list; } } });
export const todoItemQuery = selectorFamily({
key: 'todoItemQuery',
get: (id) => async ({ get }) => {
const response = await fetch(/api/todos/${id});
return response.json();
}
});
`
Pros and Cons
Pros: - Fine-grained reactivity - Minimal boilerplate - Excellent performance optimization - Built-in async support - Great for complex derived state - Concurrent mode compatible - Easy to test individual atoms
Cons: - Experimental (not production-ready as of writing) - Smaller ecosystem - Less mature tooling - Learning curve for atom/selector concepts - Facebook dependency concerns
Detailed Comparison {#comparison}
Performance Comparison
| Feature | Context API | Redux | Recoil | |---------|-------------|-------|--------| | Bundle Size | 0KB (built-in) | ~45KB (with RTK) | ~21KB | | Re-render Optimization | Manual | Good with selectors | Excellent | | Memory Usage | Low | Medium | Low | | Update Frequency | All consumers | Selective | Selective |
Learning Curve
`
Difficulty Level (1-10)
┌─────────────────────────────────────┐
│ Context API ████ │ 4/10
│ Redux ████████ │ 8/10
│ Recoil ██████ │ 6/10
└─────────────────────────────────────┘
`
Feature Matrix
| Feature | Context API | Redux | Recoil | |---------|-------------|-------|--------| | Built-in to React | ✅ | ❌ | ❌ | | DevTools | ❌ | ✅ | ✅ | | Middleware | ❌ | ✅ | ❌ | | Async Operations | Manual | With middleware | Built-in | | TypeScript Support | ✅ | ✅ | ✅ | | Time Travel | ❌ | ✅ | ❌ | | Code Splitting | ❌ | ✅ | ✅ | | Derived State | Manual | Manual | Built-in |
Real-World Project Examples {#examples}
Small E-commerce App (Context API)
Perfect for applications with simple state requirements:
`jsx
// contexts/CartContext.js
import React, { createContext, useContext, useReducer } from 'react';
const CartContext = createContext();
function cartReducer(state, action) { switch (action.type) { case 'ADD_ITEM': const existingItem = state.items.find(item => item.id === action.payload.id); if (existingItem) { return { ...state, items: state.items.map(item => item.id === action.payload.id ? { ...item, quantity: item.quantity + 1 } : item ) }; } return { ...state, items: [...state.items, { ...action.payload, quantity: 1 }] }; case 'REMOVE_ITEM': return { ...state, items: state.items.filter(item => item.id !== action.payload) }; case 'UPDATE_QUANTITY': return { ...state, items: state.items.map(item => item.id === action.payload.id ? { ...item, quantity: action.payload.quantity } : item ) }; case 'CLEAR_CART': return { ...state, items: [] }; default: return state; } }
export function CartProvider({ children }) { const [state, dispatch] = useReducer(cartReducer, { items: [] });
const addItem = (product) => { dispatch({ type: 'ADD_ITEM', payload: product }); };
const removeItem = (productId) => { dispatch({ type: 'REMOVE_ITEM', payload: productId }); };
const updateQuantity = (productId, quantity) => { dispatch({ type: 'UPDATE_QUANTITY', payload: { id: productId, quantity } }); };
const clearCart = () => { dispatch({ type: 'CLEAR_CART' }); };
const total = state.items.reduce( (sum, item) => sum + item.price * item.quantity, 0 );
const value = { items: state.items, total, addItem, removeItem, updateQuantity, clearCart };
return
export function useCart() {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart must be used within a CartProvider');
}
return context;
}
`
Medium Social Media App (Redux)
Ideal for applications with complex state interactions:
`jsx
// store/postsSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const fetchPosts = createAsyncThunk(
'posts/fetchPosts',
async ({ page, limit }, { rejectWithValue }) => {
try {
const response = await fetch(/api/posts?page=${page}&limit=${limit});
return await response.json();
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const createPost = createAsyncThunk(
'posts/createPost',
async (postData, { getState, rejectWithValue }) => {
try {
const { user } = getState();
const response = await fetch('/api/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${user.token}
},
body: JSON.stringify(postData)
});
return await response.json();
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const postsSlice = createSlice({ name: 'posts', initialState: { items: [], loading: false, error: null, currentPage: 1, hasMore: true }, reducers: { likePost: (state, action) => { const post = state.items.find(post => post.id === action.payload); if (post) { post.likes += 1; post.isLiked = true; } }, unlikePost: (state, action) => { const post = state.items.find(post => post.id === action.payload); if (post) { post.likes -= 1; post.isLiked = false; } } }, extraReducers: (builder) => { builder .addCase(fetchPosts.fulfilled, (state, action) => { state.loading = false; state.items = action.payload.page === 1 ? action.payload.posts : [...state.items, ...action.payload.posts]; state.hasMore = action.payload.hasMore; state.currentPage = action.payload.page; }) .addCase(createPost.fulfilled, (state, action) => { state.items.unshift(action.payload); }); } });
export const { likePost, unlikePost } = postsSlice.actions;
export default postsSlice.reducer;
`
Large Enterprise App (Recoil)
Best for applications requiring fine-grained state management:
`jsx
// atoms/dashboardAtoms.js
import { atom, selector, selectorFamily } from 'recoil';
export const userPermissionsState = atom({ key: 'userPermissionsState', default: [] });
export const dashboardConfigState = atom({ key: 'dashboardConfigState', default: { widgets: [], layout: 'grid', theme: 'light' } });
export const widgetDataState = selectorFamily({
key: 'widgetDataState',
get: (widgetId) => async ({ get }) => {
const permissions = get(userPermissionsState);
if (!permissions.includes(widget:${widgetId}:read)) {
throw new Error('Insufficient permissions');
}
const response = await fetch(/api/widgets/${widgetId}/data);
return response.json();
}
});
export const accessibleWidgetsSelector = selector({
key: 'accessibleWidgetsSelector',
get: ({ get }) => {
const config = get(dashboardConfigState);
const permissions = get(userPermissionsState);
return config.widgets.filter(widget =>
permissions.includes(widget:${widget.id}:read)
);
}
});
export const dashboardMetricsSelector = selector({
key: 'dashboardMetricsSelector',
get: ({ get }) => {
const widgets = get(accessibleWidgetsSelector);
return {
totalWidgets: widgets.length,
activeWidgets: widgets.filter(w => w.active).length,
lastUpdated: new Date().toISOString()
};
}
});
`
Performance Considerations {#performance}
Context API Performance Tips
`jsx
// Split contexts to prevent unnecessary re-renders
const UserContext = createContext();
const UserDispatchContext = createContext();
function UserProvider({ children }) {
const [user, dispatch] = useReducer(userReducer, initialState);
return (
// Memoize context values
function UserProvider({ children }) {
const [user, dispatch] = useReducer(userReducer, initialState);
const contextValue = useMemo(() => ({
user,
dispatch
}), [user]);
return (
`
Redux Performance Tips
`jsx
// Use RTK Query for efficient data fetching
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['User', 'Post'],
endpoints: (builder) => ({
getUsers: builder.query({
query: () => '/users',
providesTags: ['User']
}),
updateUser: builder.mutation({
query: ({ id, ...patch }) => ({
url: /users/${id},
method: 'PATCH',
body: patch
}),
invalidatesTags: ['User']
})
})
});
// Use reselect for memoized selectors import { createSelector } from '@reduxjs/toolkit';
const selectPosts = (state) => state.posts.items; const selectFilter = (state) => state.posts.filter;
export const selectFilteredPosts = createSelector(
[selectPosts, selectFilter],
(posts, filter) => {
switch (filter) {
case 'published':
return posts.filter(post => post.published);
case 'draft':
return posts.filter(post => !post.published);
default:
return posts;
}
}
);
`
Recoil Performance Tips
`jsx
// Use selectorFamily for parameterized selectors
export const userByIdSelector = selectorFamily({
key: 'userByIdSelector',
get: (userId) => ({ get }) => {
const users = get(usersState);
return users.find(user => user.id === userId);
}
});
// Implement async selectors with error boundaries
export const userPostsSelector = selectorFamily({
key: 'userPostsSelector',
get: (userId) => async ({ get }) => {
try {
const response = await fetch(/api/users/${userId}/posts);
if (!response.ok) throw new Error('Failed to fetch posts');
return await response.json();
} catch (error) {
throw error; // Will be caught by error boundary
}
}
});
`
Decision Framework {#decision-framework}
When to Use Context API
✅ Choose Context API when: - Small to medium applications (< 50 components) - Simple state structure - Minimal async operations - Team prefers built-in solutions - Bundle size is critical - State changes are infrequent
Example scenarios: - Theme switching - User authentication - Simple shopping cart - Language/locale settings
When to Use Redux
✅ Choose Redux when: - Large, complex applications - Multiple developers/teams - Complex state interactions - Need for debugging tools - Frequent state updates - Middleware requirements (logging, analytics)
Example scenarios: - E-commerce platforms - Social media applications - Data dashboards - Real-time collaborative tools
When to Use Recoil
✅ Choose Recoil when: - Need fine-grained reactivity - Complex derived state calculations - Experimental features acceptable - React Concurrent Mode usage - Performance is critical - Facebook ecosystem preference
Example scenarios: - Data visualization tools - Complex forms with dependencies - Real-time applications - Scientific/financial applications
Decision Tree
`
Start
│
├─ Is your app small/simple?
│ ├─ Yes → Context API
│ └─ No → Continue
│
├─ Do you need time-travel debugging?
│ ├─ Yes → Redux
│ └─ No → Continue
│
├─ Is performance critical?
│ ├─ Yes → Recoil
│ └─ No → Continue
│
├─ Do you have complex async operations?
│ ├─ Yes → Redux (with RTK Query)
│ └─ No → Continue
│
├─ Is the app experimental/internal?
│ ├─ Yes → Recoil
│ └─ No → Redux
`
Migration Strategies {#migration}
From Context API to Redux
`jsx
// Before (Context API)
const UserContext = createContext();
function UserProvider({ children }) { const [user, setUser] = useState(null); const login = async (credentials) => { const response = await fetch('/api/login', { method: 'POST', body: JSON.stringify(credentials) }); const userData = await response.json(); setUser(userData); };
return (
// After (Redux) const userSlice = createSlice({ name: 'user', initialState: { user: null }, reducers: { setUser: (state, action) => { state.user = action.payload; } } });
export const loginUser = createAsyncThunk(
'user/login',
async (credentials) => {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
});
return await response.json();
}
);
`
From Redux to Recoil
`jsx
// Before (Redux)
const postsSlice = createSlice({
name: 'posts',
initialState: { items: [], filter: 'all' },
reducers: {
setPosts: (state, action) => {
state.items = action.payload;
},
setFilter: (state, action) => {
state.filter = action.payload;
}
}
});
// After (Recoil) export const postsState = atom({ key: 'postsState', default: [] });
export const filterState = atom({ key: 'filterState', default: 'all' });
export const filteredPostsSelector = selector({
key: 'filteredPostsSelector',
get: ({ get }) => {
const posts = get(postsState);
const filter = get(filterState);
return filter === 'all'
? posts
: posts.filter(post => post.category === filter);
}
});
`
Conclusion {#conclusion}
Choosing the right state management solution depends on your specific project requirements, team expertise, and long-term maintenance considerations.
Summary Recommendations
1. Start with Context API for simple applications and gradually migrate if complexity grows 2. Choose Redux for large, complex applications requiring robust debugging and middleware 3. Consider Recoil for performance-critical applications where experimental features are acceptable 4. Combine solutions when appropriate - use Context for theme/auth and Redux for business logic
Future Considerations
- React 18 Features: New concurrent features may influence state management patterns - Server Components: May reduce client-side state management needs - Recoil Stability: Monitor Recoil's progression toward production readiness - Redux Toolkit Query: Consider for data fetching and caching needs
The landscape of React state management continues to evolve. Stay informed about new developments and be prepared to adapt your choices as the ecosystem matures. Remember that the best solution is the one that fits your team's needs, project requirements, and long-term maintenance strategy.
By understanding the strengths and weaknesses of each approach, you can make informed decisions that will serve your application well as it grows and evolves. Whether you choose the simplicity of Context API, the robustness of Redux, or the innovation of Recoil, focus on writing maintainable, performant code that your team can understand and extend.