Why This Matters
Picture this: you're 25 minutes into a system design interview. You've drawn some nice boxes for your services, sketched out a load balancer, maybe even mentioned a message queue. Then the interviewer leans in and asks, "So what does the schema look like for this?" You freeze. You scribble "Users table" and "Posts table" with a vague arrow between them, mumble something about foreign keys, and move on. The interviewer nods politely, but you've already lost points you'll never get back. This is where most candidates quietly fail. Data modeling is simply the act of deciding what your system stores, how those things relate to each other, and how you'll query them. It's not glamorous, but it's the foundation that every other decision sits on top of. Your API design, your caching strategy, whether you can shard your database, whether your feed query takes 5 milliseconds or 5 seconds: all of it traces back to the entities you chose and how you connected them.
Think about how Instagram serves a home feed to over two billion accounts. They didn't start with "let's pick a database." They started with the question: what are the core entities (users, posts, follows, likes), what are the relationships between them, and what's the single most important query (give me the latest posts from people I follow, sorted by time)? That access pattern drove every storage decision downstream, from denormalizing feed data into pre-materialized rows to choosing partition keys that prevent any single user from becoming a hot spot. The data model was the architecture.
Interviewers know this, and they use your data modeling instincts as a proxy for real-world experience. Junior engineers jump to drawing service boxes. Senior engineers say, "Before I sketch the architecture, let me identify the core entities and access patterns." That single sentence changes how an interviewer evaluates you. By the end of this lesson, you'll know exactly how to sketch a data model under pressure, which patterns to reach for depending on the workload, and how to narrate your choices so the interviewer hears someone who's actually built systems at scale.
How It Works
There's a five-step sequence you should internalize before your interview. Not because you'll always follow it rigidly, but because having a default order keeps you from flailing at the whiteboard when nerves kick in.
Step 1: Identify your entities. These are the nouns of your system. If someone describes a social media platform, your brain should immediately fire off: User, Post, Follow, Like. Don't overthink attributes yet. Just get the nouns on the board.
Step 2: Define their attributes. Now give each entity its key fields. A User has an id, a username, an email. A Post has an id, an author_id, content, and a timestamp. You're not writing a full DDL script here. You're sketching the fields that matter for the queries you'll eventually need to support.
Step 3: Map the relationships. This is where your model starts to have shape. A User authors many Posts (one-to-many). A User follows many Users (many-to-many, mediated by a Follow table). A User likes many Posts, and a Post can be liked by many Users (another many-to-many, mediated by a Like table). Every time you draw a relationship, you're implicitly creating a foreign key. Say that out loud. Interviewers notice.
Step 4: Determine access patterns. This step is the one candidates skip, and it's the one that matters most. Before you finalize anything, ask yourself: what are the top three queries this system needs to answer? For a social feed, those might be "get all posts from people I follow, sorted by recency," "get the like count for a post," and "check if I've already liked this post." Write those queries next to your entities. They'll drive every decision from here on out.
Step 5: Choose your storage strategy. Only now do you pick a database. The access patterns from step 4 tell you whether you need strong consistency (relational), flexible schemas (document store), fast key-value lookups (wide-column), or something else entirely. The storage choice is the output of your model, not the input.
Think of it like architecture for a building. You figure out what rooms you need and how people move between them before you pick the materials. Picking concrete vs. wood first and then trying to design rooms around that constraint leads to a weird building.
Here's what the social feed example looks like when you sketch all four entities with their fields and relationships:

Logical vs. Physical: Two Levels of the Same Model
What you just saw in that diagram is a logical data model. It's the version you'd explain to a product manager: "Users create Posts, follow other Users, and like Posts." Clean, intuitive, no implementation details.
Your physical data model is where you get concrete. Which column is the partition key? What's the composite primary key on the Follow table? Do you store created_at as a clustering column so rows come back pre-sorted? The physical model is the version that actually runs on hardware.
Interview tip: Start with the logical model. Sketch entities and relationships in the first two minutes. Then, when the interviewer pushes on performance or scale, shift into physical modeling. This transition signals that you can operate at both levels of abstraction, which is exactly what senior engineers do.
You don't need to announce "now I'm switching to the physical model." Just naturally start saying things like "I'd partition this table by user_id" or "we need a composite index here." The interviewer will recognize the shift.
The Three Structural Decisions That Actually Matter
Once your entities and relationships are on the board, three choices determine whether your system will perform well or fall over.
Primary keys define how each row is uniquely identified and, in distributed databases, how data gets partitioned across nodes. For the Follow table, a composite primary key of (follower_id, followee_id) does double duty: it enforces uniqueness (you can't follow someone twice) and it lets you efficiently query "who does this user follow?" by scanning all rows with a given follower_id prefix.
Foreign keys encode your relationships. When Post has an author_id that references User.id, you've made the one-to-many relationship explicit. In a relational database, this gives you referential integrity for free. In a NoSQL store, you won't get that enforcement, so you'll need to mention how your application layer handles orphaned references. Either way, calling out the foreign key relationship shows the interviewer you're thinking about data integrity, not just data storage.
Indexes are where you turn your access patterns into fast queries. Go back to those three queries you identified in step 4. "Get all posts from people I follow, sorted by recency" means you need an index on Post's (author_id, created_at DESC). "Check if I've already liked this post" means the Like table's composite primary key on (user_id, post_id) already covers that lookup. Saying "we'll index on author_id and created_at for the feed query" is a completely different signal than saying "we'll query the posts table." The first sounds like someone who's operated a production system. The second sounds like someone who's read about them.
Common mistake: Candidates add indexes to every column "just in case." Indexes aren't free. Each one costs write performance and storage. In your interview, justify each index by tying it to a specific access pattern. One sentence is enough: "We need this index because our feed query filters on X and sorts by Y."
Your 30-second explanation: "Data modeling is the process of identifying your core entities, their attributes, and how they relate to each other, then designing your schema around the access patterns your system needs to support. You start with the logical model (what are the nouns and how do they connect), move to the physical model (partition keys, indexes, storage engine), and let your read/write patterns drive every structural choice. The model comes first; the database choice comes last."
Patterns You Need to Know
In an interview, you'll usually need to pick a specific approach. Here are the ones worth knowing.
Each of these patterns solves a different shape of problem. Your job isn't to memorize them like flashcards; it's to recognize which access patterns call for which model, and then explain why to your interviewer in plain language.
Normalized Relational Modeling
Every entity lives in its own table. Relationships are expressed through foreign keys. No data is duplicated anywhere. If a customer's name changes, you update it in exactly one row, and every order that references that customer automatically reflects the change.
This is the default mental model most engineers carry around, and for good reason. When you need strong consistency (think: financial transactions, inventory management, order processing), normalization gives you clean writes and a single source of truth for every piece of data. An e-commerce order system is the textbook example: Customer, Order, OrderItem, and Product each get their own table, connected by foreign keys. You can answer questions like "what did this customer order?" or "how many units of this product sold?" without worrying about stale copies floating around.
The cost shows up on reads. To render an order confirmation page, you're joining across three or four tables. At moderate scale, that's fine. At millions of requests per second, those joins become your bottleneck. Your interviewer knows this. They're waiting to see if you know it too.
Interview tip: When you sketch a normalized model, say something like: "I'm starting normalized because consistency matters here. If read latency becomes a problem at scale, I'll denormalize the hot paths." That one sentence shows you understand both the pattern and its limits.
When to reach for this: Any system where correctness on writes matters more than raw read speed. E-commerce, banking, booking systems, anything with money changing hands.

Denormalized / Read-Optimized Modeling
Picture this: your social media feed needs to show posts from everyone you follow, sorted by time, with the author's display name and avatar URL right there in the feed. In a normalized model, that's a join across the Follow table, the Post table, and the User table, for every single feed load, for every single user. At scale, that query is a disaster.
Denormalization flips the tradeoff. Instead of assembling data at read time, you pre-assemble it at write time. When someone publishes a post, a fan-out worker copies the relevant data (author name, content preview, timestamp) into a FeedItem table for each of that author's followers. Now reading the feed is a single-partition scan. No joins. The read path is trivially fast.
The price? Writes get more complex. If a user changes their display name, you might have millions of FeedItem rows with the old name embedded in them. You need a strategy for that: maybe you accept eventual consistency and backfill lazily, or maybe you store author_id in the FeedItem and only cache the name at the application layer. These are exactly the kinds of tradeoffs interviewers want to hear you reason through.
Common mistake: Candidates sometimes treat denormalization as "wrong" or a last resort. In read-heavy systems at scale, it's the expected answer. If the interviewer says "this system handles 100K reads per second and 500 writes per second," they're practically handing you a sign that says "please denormalize."
When to reach for this: Read-heavy workloads where latency matters. News feeds, timelines, dashboards, product listing pages. Basically any time you hear "high read-to-write ratio" in the problem statement.

Document-Oriented Modeling
Not every entity fits neatly into rows and columns. A product catalog is a great example: a laptop has specs like RAM, CPU, and screen size. A t-shirt has size, color, and material. A book has ISBN, page count, and author. Trying to force all of these into a single relational schema means either a very wide table full of NULLs, or an awkward entity-attribute-value pattern that's painful to query.
Document-oriented modeling sidesteps this entirely. Each product is stored as a self-contained document (think JSON) with whatever fields it needs. Variants, specifications, and images can be nested right inside the document. Your application reads the whole thing in one fetch. No joins, no assembly. The document is the API response, more or less.
This works beautifully when your access pattern is "fetch one thing by its ID and get everything about it." It starts to struggle when you need to query across documents in complex ways, or when deeply nested data needs to be updated independently. If you find yourself saying "I need to update just the stock count of one variant inside one product," that nested write is more awkward than it would be in a relational model.
When to reach for this: Variable-schema entities, content management systems, product catalogs, user profiles with optional or heterogeneous fields. If the interviewer describes entities that don't share a uniform structure, this is your cue.

Time-Series / Append-Only Modeling
Metrics dashboards, activity logs, IoT sensor data, event sourcing. All of these share a common shape: data arrives continuously, is almost never updated, and gets queried by time range.
The key design decision here is your partition key. If you partition only by entity_id (say, a server or user), a single popular entity can create a hot partition that overwhelms one node. If you partition only by time, a single time bucket gets hammered during peak hours. The standard solution is a composite partition key: entity_id plus a time bucket (like a date or hour). This spreads writes across partitions while still letting you efficiently scan "all events for user X in the last 24 hours."
Raw events pile up fast, though. Nobody wants a dashboard that scans through 500 million raw rows to compute a weekly average. That's where rollup tables come in. A periodic aggregation job reads raw events, computes summaries (counts, averages, percentiles), and writes them into a separate table keyed by entity, time period, and metric name. The dashboard reads from the rollup table; the raw events stick around for debugging or reprocessing.
Key insight: When an interviewer asks you to "design a metrics system" or "design an analytics pipeline," they're testing whether you understand that raw storage and query-time aggregation are two separate problems. Mention rollup tables early and you'll immediately stand out.
When to reach for this: Any problem involving event logs, analytics, monitoring, or audit trails. If the word "dashboard" appears in the problem statement, this pattern is almost certainly part of your answer.

Adjacency List / Graph-Style Modeling
Social graphs, org charts, permission hierarchies, recommendation engines. All of these boil down to nodes connected by edges. You can model this in a relational database with two tables: a Node table (id, type, name, metadata) and an Edge table (source_id, target_id, edge_type, created_at). Want to find all of a user's friends? Query the Edge table where source_id equals the user and edge_type equals "friend." Want mutual friends? It's a self-join on the Edge table.
This adjacency list approach works surprisingly well for one-hop and two-hop queries. "Who does Alice follow?" is fast. "Do Alice and Bob have mutual friends?" is a manageable self-join. But the moment you need multi-hop traversals ("find all users within 3 degrees of Alice" or "what's the shortest path between these two nodes?"), relational databases start to choke. Each hop is another join, and the query plan explodes.
That's the inflection point where you'd argue for a dedicated graph database like Neo4j or Amazon Neptune. In an interview, you don't need to commit to one immediately. Start with the adjacency list in your relational store, explain that it handles your primary access patterns, and then say: "If we find ourselves doing frequent multi-hop traversals, we'd migrate the graph data to a purpose-built graph database. For now, the adjacency list keeps our operational complexity low."
When to reach for this: Any problem involving relationships between entities of the same type, or hierarchical structures. Social networks, access control systems, org charts, "people you may know" features.

| Pattern | Best For | Read Characteristics | Write Characteristics |
|---|---|---|---|
| Normalized relational | Transactional systems, consistency-first | Joins required; slower at scale | Clean, single-point updates |
| Denormalized / read-optimized | High-read, low-write workloads | Single-partition scans, very fast | Complex fan-out, data duplication |
| Document-oriented | Variable schemas, fetch-by-ID access | Single-document fetch, no joins | Nested updates can be awkward |
| Time-series / append-only | Logs, metrics, event streams | Time-range scans, rollup queries | Append-only, very high throughput |
| Adjacency list / graph | Relationships, hierarchies, networks | Fast for 1-2 hops; degrades beyond | Simple edge inserts |
For most interview problems, you'll default to a normalized relational model and then selectively denormalize the read-heavy paths. That combination covers probably 70% of system design questions. Reach for the document model when the interviewer describes entities with wildly different schemas, and reach for time-series modeling the moment you hear "analytics," "metrics," or "activity log." The adjacency list is your tool for any problem where the relationships between entities are the product, not just a supporting detail.
What Trips People Up
Here's where candidates lose points, and it's almost always one of these.
The Mistake: Drawing Boxes Before Sketching Entities
The candidate jumps to the whiteboard and immediately draws "API Gateway → Feed Service → Post Service → Database." Five minutes later, they've committed to a microservice architecture with three separate databases, and they still haven't defined what a Post actually looks like or how it relates to a User.
This is backwards. Once you've drawn those boxes, you're psychologically locked in. When the interviewer asks "how does the feed service get the data it needs?", you're now trying to retrofit a data model into an architecture that may not support it. Maybe your Feed Service needs data that lives in two other services, and now you're stuck designing cross-service joins or synchronization patterns that wouldn't exist if you'd started from the data.
Common mistake: Candidates say "Let me start with the high-level architecture" and spend 10 minutes on service boundaries before touching data. The interviewer hears: "This person designs systems top-down without understanding what's actually being stored."
Flip the order. Spend two minutes identifying your core entities, their relationships, and the two or three queries that matter most. Then let that shape which services you need and where the data lives. You'll make better architectural decisions, and you'll look like someone who's actually built production systems.
The Mistake: Never Saying Your Access Patterns Out Loud
A candidate designs a perfectly normalized schema. Users table, Posts table, Follows table, Likes table. Foreign keys everywhere. Textbook relational modeling. The interviewer nods, then asks: "So how do you render the home feed?"
Silence. Then fumbling. The candidate realizes that fetching a feed means joining Users to Follows to Posts, then left-joining Likes to get like counts, sorting by recency, and paginating. That's a multi-table join on every single page load for every single user.
The fix is simple: before you finalize any schema, say your top queries out loud. Literally narrate them. "The most frequent read is: given a user_id, fetch the 20 most recent posts from people they follow, with author names and like counts. The most frequent write is: a user publishes a new post." When you do this, schema problems become obvious immediately. You'll catch the six-table join before the interviewer does.
Interview tip: Try saying: "Let me think about the primary access patterns before I lock in this schema. Our hottest read path is [X], and our hottest write path is [Y]. Let me make sure this model supports both efficiently."
This one sentence signals more design maturity than ten minutes of drawing ER diagrams.
The Mistake: Treating Normalization as the Only "Correct" Answer
Some candidates treat denormalization like a dirty word. They design a fully normalized schema, and when the interviewer pushes on read performance at scale, they reach for caching or read replicas instead of reconsidering the data model itself.
Here's the thing: in an interview about a system handling millions of users, the interviewer is often waiting for you to suggest denormalization. They want to hear you reason about the tradeoff. When you stubbornly stick to third normal form for a read-heavy social feed, you signal that you've mostly worked on small-scale applications where joins are cheap and consistency is the only concern.
That doesn't mean you should denormalize everything. The winning move is to start normalized, identify the read path that breaks under load, and then selectively denormalize with clear justification. "This feed query hits three tables on every request. I'd create a pre-materialized feed_items table with the author name embedded directly. Yes, that means when a user changes their display name, we need to update it across their feed entries. But that's a rare write compared to millions of feed reads per second."
Common mistake: Candidates say "We should keep this normalized for data integrity." The interviewer hears: "This person doesn't know how to make tradeoffs for scale."
The Mistake: Picking the Database Before Designing the Model
"We'll use MongoDB for this." That's the first thing out of the candidate's mouth, before a single entity has been identified or a single access pattern discussed.
This is a red flag for interviewers. It suggests you have a favorite tool and you're going to make the problem fit it, rather than letting the problem guide your technology choice. Worse, it often leads to contorted data modeling. The candidate picked a document store, so now they're trying to cram a highly relational domain (orders referencing customers referencing products) into nested documents, creating update anomalies everywhere.
Your entity structure and access patterns should come first. The database choice should feel like a natural conclusion, not a premise. If your data is highly relational with strong consistency requirements, say so, and then say that points you toward a relational database like PostgreSQL. If your access is all key-value lookups partitioned by a known key with time-range scans, say that, and explain why a wide-column store fits. The reasoning matters more than the answer.
Interview tip: When you do name a database, tie it directly to your model: "Our primary access pattern is single-key lookups with range scans on time. The data is write-heavy and doesn't need cross-partition joins. That's a natural fit for Cassandra's partition model, where we'd use user_id as the partition key and event_time as the clustering column."
That's a data-driven technology choice. It sounds completely different from "let's use Cassandra because it scales."
How to Talk About This in Your Interview
Most candidates wait for the interviewer to ask "what's your schema?" before they think about data. You want to be the person who brings it up first, unprompted, within the first few minutes. That single move changes how the interviewer perceives everything that follows.
When to Bring It Up
The moment you finish clarifying requirements and are about to sketch architecture, pause. That transition point, right before you draw your first box, is when you say: "Before I lay out services, let me identify the core entities and how they relate to each other."
Beyond that opening, listen for these cues throughout the interview:
- "How would you store this?" They're not asking you to name a database. They want to see entities, fields, and relationships.
- "Walk me through a read/write flow." This is your signal to ground the conversation in your schema. Reference specific columns and indexes, not abstract "we query the database" hand-waving.
- "What happens at scale?" Nine times out of ten, the scaling bottleneck traces back to a data modeling decision. This is where you bring up denormalization, partition keys, or index strategy.
- "What database would you use?" Resist the urge to blurt out a technology name. Reframe it: "Let me describe the access patterns first, and that'll point us to the right storage engine."
If you hear the interviewer say "let's not worry about the schema" or "assume the data layer is handled," respect that. They're telling you they want to spend time elsewhere. But this is rare.
Sample Dialogue
Here's how this sounds in practice. Notice how the candidate steers toward data modeling early and handles pushback without getting defensive.
You: "OK, so we need to support a home feed, posting, and following. Before I draw out services, I want to sketch the core entities. We've got User, Post, Follow, and Like. The key relationship is that Follow is a many-to-many between users, and that's what drives the feed query. Let me jot down the fields that matter..."
Interviewer: "Sure, go for it. But why are you starting here instead of the high-level architecture?"
You: "Because the shape of this data is going to dictate which services we need and how they communicate. For example, if I know the feed query is 'give me the 50 most recent posts from people I follow, sorted by time,' that tells me whether I need a fan-out service or if I can get away with a read-time join. I'd rather figure that out now than redesign halfway through."
Interviewer: "OK, makes sense. So you've got Follow as its own table. But that means fetching a user's feed requires joining Follow to Post for every request. At 50 million users, that's going to be painful."
You: "Exactly, and that's why I wouldn't serve the feed from these normalized tables directly. Our primary read pattern is 'fetch feed for user X, sorted by recency.' Doing that join on every request would be brutal. I'd denormalize by maintaining a FeedItem table, keyed on feed_owner_id and post_id, with the author name and content preview embedded. A fan-out worker writes to this table whenever someone publishes a post. We trade write amplification for single-table feed reads."
Interviewer: "What if a user changes their display name? Now you've got stale data in millions of feed rows."
You: "Yeah, that's the cost of denormalization. For display names, I'd accept eventual consistency. We can backfill asynchronously, or just update it on new writes and let old feed items show the previous name. Users change their name rarely enough that this is a reasonable tradeoff. If the interviewer... sorry, if the product requires strict consistency on names, we could store just the author_id in FeedItem and do a lightweight lookup against a cached user profile instead of a full join."
Notice how that last exchange got a little messy. The candidate almost broke the fourth wall, corrected themselves, and kept going. That's fine. Interviews are conversations, not presentations.
Follow-Up Questions to Expect
"How would you handle schema migrations as features evolve?" Point to your design's flexibility: "I'd keep a JSONB metadata column for extensible fields, so adding reactions or bookmarks doesn't require a migration across billions of rows."
"Why not just use a graph database for the social relationships?" Tie it to operational complexity: "An adjacency list in Postgres handles our access patterns with simple indexed lookups. I'd only reach for Neo4j or similar if we needed multi-hop traversals like 'friends of friends of friends,' which isn't in our requirements."
"How do you decide what to index?" Narrate from the query, not from theory: "Our feed query filters on feed_owner_id and sorts by created_at, so we need a composite index on (feed_owner_id, created_at DESC). Without it, we're doing a full table scan on every feed load."
"What's your partition key strategy?" Show you're thinking about data distribution: "Partitioning FeedItem by feed_owner_id keeps all of one user's feed on the same node, which makes the read a single-partition query. The risk is celebrity accounts creating hot partitions, which I'd handle with a separate fan-out strategy for users above a follower threshold."
What Separates Good from Great
- A mid-level answer names entities and draws boxes with arrows. A senior answer states the top two or three access patterns before finalizing the schema, then shows how the schema serves those patterns. The difference is whether your model is driven by queries or by intuition.
- Mid-level candidates pick a database and then design a model that fits it. Senior candidates describe their access patterns, explain the read/write ratio, mention whether they need strong consistency or can tolerate eventual consistency, and then say "given all that, a wide-column store like Cassandra fits because..." The database choice becomes a conclusion, not a starting assumption.
- Great candidates know when to stop. Two to four minutes on the data model is the sweet spot. Sketch your entities, call out the primary keys and one or two critical indexes, state your access patterns, and move on. If the interviewer wants you to go deeper, they'll ask. You're demonstrating that you think in data models, not producing a complete DDL script.
Key takeaway: Sketch your entities and access patterns before you draw a single architecture box; the interviewer will remember that you started with the data, and every design decision after that will sound more grounded because of it.
