Lists & Keys
Rendering lists of data is one of the most common tasks in React. Understanding how to do it correctly and efficiently is crucial for building performant applications. Real-world analogy for keys: Imagine a teacher taking attendance in a classroom. If students do not have names (no keys), and one student leaves, the teacher has to re-check every seat from the beginning. But if every student has a name badge (a key), the teacher instantly knows which student left and which ones just shifted seats. That is exactly what React’skey prop does — it gives each list item an identity so React can efficiently track additions, removals, and reorderings without rebuilding the entire list.
Rendering Lists with map()
Use JavaScript’smap() method to transform an array of data into an array of elements.
Rendering Objects
Understanding Keys
Keys are special string attributes you need to include when creating lists of elements. They help React identify which items have changed, been added, or been removed.Why Keys Matter
React uses keys during its reconciliation process to efficiently update the DOM:Key Rules
- Keys must be unique among siblings (not globally unique)
- Keys should be stable (same item = same key across renders)
- Keys should not change (don’t use random values)
What to Use as Keys
The Index Key Problem
Using array index as key causes one of the most frustrating bugs in React: state gets associated with the wrong item when the list order changes. This happens because React uses the key to match old and new elements. If the key stays the same but the item at that position changes, React reuses the old component instance (including its internal state) for a completely different item.Filtering Lists
Sorting Lists
Search and Filter Combined
Nested Lists
product.id can be the same number as category.id because they’re in different levels.Rendering Nothing for Some Items
Usefilter() before map(), or return null from map:
Empty States
Always handle the case when a list is empty:Performance: Extracting List Items
For complex lists, extract the item into its own component:🎯 Practice Exercises
Exercise 1: Todo List with CRUD
Exercise 1: Todo List with CRUD
Exercise 2: Filterable Contact List
Exercise 2: Filterable Contact List
Exercise 3: Reorderable List
Exercise 3: Reorderable List
Summary
Next Steps
Interview Deep-Dive
You have a list of 10,000 items that users can reorder via drag-and-drop. Using array index as the key, users report that input fields inside list items lose their text on reorder. Diagnose the issue and fix it.
You have a list of 10,000 items that users can reorder via drag-and-drop. Using array index as the key, users report that input fields inside list items lose their text on reorder. Diagnose the issue and fix it.
key={item.id}, when item B moves to position 0, React sees key=“b-uuid” at position 0 (which was previously at position 1) and moves the existing component instance with its correct state.For the performance angle with 10,000 items: proper keys combined with React.memo on the list item component means React only updates the items that actually moved — typically just 2 items in a single drag operation. With index keys, React would diff and potentially update all 10,000 items because every key-to-data mapping changed.In production, I would also add list virtualization (react-window or react-virtuoso) for 10,000 items. You only render the visible 20-50 items, so even if reconciliation is triggered, only a handful of components participate.Follow-up: Can you ever safely use index as a key? What are the exact conditions?Three conditions must all be true: the list is static (items are never added, removed, or reordered), the items have no internal state (no inputs, no expanded/collapsed states), and the items are not referenced by other components. A list of static labels that never changes meets all three. A navigation menu with fixed items meets all three.The moment any condition is violated, index keys become dangerous. In practice, I default to unique IDs because the cost is negligible and the safety benefit is significant. The only exception I make is for truly ephemeral rendering where the list is generated fresh each time and has no interactivity — like rendering pagination dots or star ratings.How does React's reconciliation algorithm handle list reordering differently from list insertion? What is the time complexity?
How does React's reconciliation algorithm handle list reordering differently from list insertion? What is the time complexity?
performance.mark() and performance.measure() around the state update, or React’s Profiler component with an onRender callback that logs commit times. Run each scenario 50 times and compare the median commit duration. On a 5,000-item list with frequent reordering, I would expect ID keys to be 10-50x faster in DOM mutations, though the JavaScript diffing cost remains similar.When rendering a filtered or sorted list, should you filter/sort in the render body, in useMemo, or in a useEffect? Explain the tradeoffs.
When rendering a filtered or sorted list, should you filter/sort in the render body, in useMemo, or in a useEffect? Explain the tradeoffs.
const filtered = items.filter(i => i.active). This is simple, always up to date, and for most list sizes (under a few hundred items), the performance cost is negligible. React will run this on every render, but filtering an array of 100 objects takes microseconds.The useMemo approach — const filtered = useMemo(() => items.filter(i => i.active), [items]) — is appropriate when the computation is genuinely expensive. Sorting 10,000 objects with a complex comparator, chaining multiple filters, or computing derived aggregations (group by, reduce) are good candidates. useMemo caches the result and only recomputes when items changes, skipping the work on unrelated re-renders (like typing in a search field that does not affect the items array).The useEffect approach — storing filtered results in separate state — is almost always wrong. It introduces an unnecessary state variable, causes an extra render (the first render has stale filtered data, the effect runs after paint and triggers a second render with correct data), and creates a synchronization bug if you forget a dependency. The React docs explicitly call this an anti-pattern: “If you can calculate something from the existing props or state, don’t put it in state. Instead, calculate it during rendering.”The one legitimate use of useEffect for derived data is when the derivation has an asynchronous step — like filtering requires a server call. But that is data fetching, not derivation.Follow-up: If you use useMemo for a filtered list, what happens if the items array is the same data but a new reference every render? How do you fix this?If the parent recreates the array on every render (e.g., items={data.map(x => ({ ...x }))}) the useMemo re-runs every time because [items] has a new reference. The memoization is worthless.The fix depends on where the array comes from. If it comes from a parent via props, the parent should memoize it: const items = useMemo(() => data.map(transform), [data]). If it comes from a Redux selector, useSelector already uses reference equality by default, so the selector should return the same reference if the underlying data has not changed — use createSelector from Reselect for derived data. If it comes from a useState, ensure you only call setState with a new array when the data actually changes, not on every render.The general principle: memoization only helps when the inputs are stable. If the inputs are new on every render, memoization adds overhead (comparing dependencies) without saving any work.