๐The Interview Setting
Company: Twitter/X Round: Frontend System Design Duration: 45 minutes
๐๐ค Interviewer: "Design the Twitter home feed with real-time updates."
Candidate: "Let me clarify requirements first..."
Questions asked:
Interviewer: "Real-time with WebSocket. Include likes/retweets. Skip compose. Yes to basic offline."
๐๐ Component Architecture
Feed|+-- FeedHeader| +-- NewTweetsIndicator|+-- TweetList (virtualized)| +-- TweetCard| +-- UserAvatar| +-- TweetContent| | +-- Text| | +-- MediaGallery| | +-- LinkPreview| +-- TweetActions| | +-- ReplyButton| | +-- RetweetButton| | +-- LikeButton| | +-- ShareButton| +-- TweetMetrics|+-- LoadMoreTrigger
๐๐ค Interviewer: "How do you handle real-time updates without disrupting scroll?"
Candidate:
const useFeed = () => { const [tweets, setTweets] = useState([]); const [newTweets, setNewTweets] = useState([]); const [isAtTop, setIsAtTop] = useState(true); // WebSocket connection useEffect(() => { const ws = new WebSocket('wss://api.twitter.com/feed'); ws.onmessage = (event) => { const newTweet = JSON.parse(event.data); if (isAtTop) { // User at top - add directly setTweets(prev => [newTweet, ...prev]); } else { // User scrolling - queue tweets setNewTweets(prev => [newTweet, ...prev]); } }; return () => ws.close(); }, [isAtTop]); const showNewTweets = () => { setTweets(prev => [...newTweets, ...prev]); setNewTweets([]); window.scrollTo({ top: 0, behavior: 'smooth' }); }; return { tweets, newTweets, showNewTweets, setIsAtTop };};New Tweets Indicator:
{newTweets.length > 0 && ( <button onClick={showNewTweets} className="fixed top-16 left-1/2 -translate-x-1/2 bg-blue-500 text-white px-4 py-2 rounded-full shadow-lg" > Show {newTweets.length} new tweets </button>)}
๐๐ค Interviewer: "How do you implement optimistic UI for likes?"
Candidate: "Update UI immediately, rollback on failure..."
const useLike = (tweetId) => { const [isLiked, setIsLiked] = useState(false); const [likeCount, setLikeCount] = useState(0); const [isLoading, setIsLoading] = useState(false); const toggleLike = async () => { // Optimistic update const previousState = { isLiked, likeCount }; setIsLiked(!isLiked); setLikeCount(prev => isLiked ? prev - 1 : prev + 1); try { setIsLoading(true); if (isLiked) { await unlikeTweet(tweetId); } else { await likeTweet(tweetId); } } catch (error) { // Rollback on failure setIsLiked(previousState.isLiked); setLikeCount(previousState.likeCount); toast.error('Failed to update. Please try again.'); } finally { setIsLoading(false); } }; return { isLiked, likeCount, toggleLike, isLoading };};
๐๐ค Interviewer: "What edge cases for infinite scroll?"
Candidate:
1. Duplicate Prevention
const addTweets = (newTweets) => { setTweets(prev => { const existingIds = new Set(prev.map(t => t.id)); const uniqueNew = newTweets.filter(t => !existingIds.has(t.id)); return [...prev, ...uniqueNew]; });};2. Gap Detection (User was offline)
const [hasGap, setHasGap] = useState(false);// On reconnect, check if we missed tweetsconst checkForGap = async () => { const latestId = tweets[0]?.id; const { missedCount } = await api.checkGap(latestId); if (missedCount > 0) { setHasGap(true); }};3. Memory Management
// Keep only last 500 tweets in memoryconst MAX_TWEETS = 500;const addTweet = (tweet) => { setTweets(prev => { const updated = [tweet, ...prev]; return updated.slice(0, MAX_TWEETS); });};4. Scroll Position Restoration
const ScrollRestoration = () => { const location = useLocation(); useEffect(() => { const savedPosition = sessionStorage.getItem(`scroll-${location.key}`); if (savedPosition) { window.scrollTo(0, parseInt(savedPosition)); } return () => { sessionStorage.setItem(`scroll-${location.key}`, window.scrollY); }; }, [location]);};5. Network Retry with Backoff
const fetchWithRetry = async (url, retries = 3) => { for (let i = 0; i < retries; i++) { try { return await fetch(url); } catch (error) { if (i === retries - 1) throw error; await sleep(Math.pow(2, i) * 1000); } }};
๐๐ค Interviewer: "How do you handle media-heavy tweets?"
Candidate:
const MediaGallery = ({ media }) => { return ( <div className="grid gap-1"> {media.map((item, index) => ( <LazyMedia key={item.id} src={item.url} blurhash={item.blurhash} aspectRatio={item.aspectRatio} // Load when 200px from viewport rootMargin="200px" /> ))} </div> );};const LazyMedia = ({ src, blurhash, aspectRatio, rootMargin }) => { const [isLoaded, setIsLoaded] = useState(false); const [isVisible, setIsVisible] = useState(false); const ref = useRef(); useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsVisible(true); observer.disconnect(); } }, { rootMargin } ); if (ref.current) observer.observe(ref.current); return () => observer.disconnect(); }, [rootMargin]); return ( <div ref={ref} style={{ aspectRatio }}> {/* Blurhash placeholder */} {!isLoaded && <Blurhash hash={blurhash} />} {/* Actual image */} {isVisible && ( <img src={src} onLoad={() => setIsLoaded(true)} loading="lazy" decoding="async" className={isLoaded ? 'opacity-100' : 'opacity-0'} /> )} </div> );};
๐๐ Interview Scoring
Result: Strong Hire ๐