๐Ÿ‡ฎ๐Ÿ‡ณ
๐Ÿ‡ฎ๐Ÿ‡ณ
Republic Day Special Offer!Get 20% OFF on all courses
Enroll Now
P
Prakalpana
๐Ÿ“šLearn
โ€ขCode Your Future
Interview Prepโฑ๏ธ 22 min read๐Ÿ“… Dec 23

UI Interview: Design Twitter/X Feed (Complete Mock)

SR
Sneha Reddyโ€ขSenior Engineer at X
๐Ÿ“‘ Contents (13 sections)

๐Ÿ“Œ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:

  • 1Real-time or polling for new tweets?
  • 2Features: likes, retweets, replies, media?
  • 3Should I handle tweet composition?
  • 4Offline support needed?
  • 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 tweets
    const 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 memory
    const 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

    CriteriaScore

    Real-time Architectureโœ… Optimistic Updatesโœ… Edge Casesโœ… Performanceโœ… Offline Handlingโœ…

    Result: Strong Hire ๐ŸŽ‰

    SR

    Written by

    Sneha Reddy

    Senior Engineer at X

    ๐Ÿš€ Master Interview Prep

    Join 500+ developers

    Explore Courses โ†’
    Chat on WhatsApp