The 60 Apache Cassandra questions interviewers actually ask, with direct answers, runnable CQL, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.
60 questions with answersKey Takeaways
Apache Cassandra is an open-source, distributed NoSQL database first built at Facebook in 2008 and now an Apache project. It stores data in a wide-column model, spreads it across a masterless ring of equal nodes, and prizes availability and horizontal scale over strict consistency. There's no primary node, so writes go to any node and replicate to others, which is why Cassandra keeps serving traffic even when machines fail. It handles very high write volumes on commodity hardware, which is why teams like Netflix, Apple, and Discord have run it at scale. In interviews, Cassandra questions probe query-first data modeling, how the partition key controls distribution, tunable consistency levels, and the write and read path (commit log, memtable, SSTable, compaction), not memorized commands. This page collects the 60 questions that come up most, each with a direct answer and runnable CQL. If you're building fundamentals, the official Apache Cassandra documentation is the canonical reference; increasingly the first round runs as a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.
Watch: Apache Cassandra Database: Full Course for Beginners
Video: Apache Cassandra Database: Full Course for Beginners (freeCodeCamp.org, YouTube)
Test yourself and earn a certificate
10 quick questions. Score 70%+ to download your Cassandra certificate.
The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.
Cassandra is an open-source, distributed, wide-column NoSQL database designed for high write throughput and no single point of failure. It spreads data across a ring of equal nodes so it scales horizontally and stays available when machines fail.
It fits write-heavy, always-on workloads: time-series data, event logs, messaging, IoT, and recommendation feeds. Teams pick it when they need to scale writes across many machines and multiple datacenters more than they need joins or ad-hoc queries.
Key point: A one-line definition plus one concrete workload (time-series, messaging) beats a feature list. Interviewers open with this to hear how you frame a technology.
Watch a deeper explanation
Video: Crash Course | Introduction to Cassandra for Developers (DataStax Developers, YouTube)
Cassandra has no joins, no foreign keys, and no strong-by-default transactions across rows. You model tables around queries and denormalize, where a relational database normalizes and joins at query time. Cassandra scales writes horizontally across equal nodes; a relational database usually has one primary node for writes.
The trade-off: Cassandra gives you scale and availability, and gives up the flexible querying and strict consistency SQL databases provide.
| Cassandra | Relational (SQL) | |
|---|---|---|
| Data model | Wide-column, denormalized | Normalized tables |
| Joins | None | Yes |
| Scaling | Horizontal, masterless | Mostly vertical, one primary |
| Query freedom | Query-first, limited WHERE | Ad-hoc SQL |
Key point: The follow-up is 'so when would you pick Cassandra over Postgres?'. Have the write-scale and availability answer ready.
A keyspace is the top-level container for tables, like a database or schema in SQL. It's where you set the replication strategy and replication factor, which then apply to every table inside it. You create it before any table and it defines how data in it gets copied across the cluster.
You almost always create a keyspace before any table, choosing NetworkTopologyStrategy for production so replicas spread across datacenters and racks.
CREATE KEYSPACE store
WITH replication = {
'class': 'NetworkTopologyStrategy',
'dc1': 3
};
USE store;A Cassandra primary key has two parts: a partition key and zero or more clustering columns. The partition key decides which node stores the row, and the clustering columns set the sort order of rows inside that partition. Both together must uniquely identify a row, and both shape how you can query the table.
The first component (or the parenthesized group) is the partition key; everything after it clusters. This single line of a CREATE TABLE controls both distribution and on-disk ordering, so it's the most important decision in a schema.
CREATE TABLE messages (
room_id uuid,
sent_at timestamp,
body text,
PRIMARY KEY (room_id, sent_at)
);
-- room_id partitions; sent_at clusters (sorts) within a roomKey point: the question needs to hear 'partition key plus clustering columns' explicitly. Blurring the two is the most common junior mistake.
Watch a deeper explanation
Video: 5 Steps to an Awesome Apache Cassandra Data Model | DataStax (DataStax, YouTube)
The partition key is the part of the primary key Cassandra hashes to decide which node owns a row. All rows sharing a partition key land in the same partition on the same replica set, so they're stored and read together.
It matters because it controls both distribution and query efficiency. Pick it well and load spreads evenly and your reads hit one node; pick it badly and you get hot partitions or queries that scan the whole cluster.
Key point: The two jobs of a partition key is explicit: even distribution and single-partition reads. That framing is what earns the point.
Clustering columns are the primary-key components that come after the partition key. They set the order in which rows are physically stored inside a partition, which lets Cassandra return sorted results and answer range queries efficiently without a sort at read time. They also make each row within a partition unique.
You control direction with WITH CLUSTERING ORDER BY. A common pattern is sorting newest-first so recent rows come back without a sort at read time.
CREATE TABLE events (
user_id uuid,
ts timestamp,
action text,
PRIMARY KEY (user_id, ts)
) WITH CLUSTERING ORDER BY (ts DESC);Watch a deeper explanation
Video: Apache Cassandra Database: Full Course for Beginners (freeCodeCamp.org, YouTube)
CQL (Cassandra Query Language) is Cassandra's query language, and it looks a lot like SQL on the surface: you write CREATE TABLE, INSERT, SELECT, UPDATE, and DELETE. The familiar syntax means relational developers get started fast without learning a whole new vocabulary for basic operations.
The similarity is only skin deep. CQL has no joins, no subqueries, and a WHERE clause that's restricted to the primary key unless you opt into filtering. It describes single-table, query-first access, not relational algebra.
INSERT INTO events (user_id, ts, action)
VALUES (now(), toTimestamp(now()), 'login');
SELECT action, ts FROM events
WHERE user_id = 5b5a...;The replication factor (RF) is how many copies of each partition Cassandra keeps across the cluster, and it's configured per keyspace. RF=3 means every row lives on three separate replica nodes. That redundancy is what lets Cassandra survive node failures and still serve every read and write.
A higher RF means more fault tolerance and more storage. RF=3 is the common production choice: the cluster keeps serving even if a node goes down, and QUORUM operations still work with one replica offline.
Key point: RF connects to availability in one sentence: RF=3 lets you lose a node and still read and write at QUORUM. That link is the answer.
A wide-column store organizes data into rows and columns, but each partition can hold a very large number of rows and columns, and different rows don't need identical columns. Cassandra groups rows by partition key rather than storing one flat table.
Think of it as a map of partition key to an ordered set of rows. It's not a document store and not a plain key-value store; the model sits between them and is built for reading a partition's worth of related rows quickly.
A node is a single Cassandra instance storing part of the data. A cluster is the full set of nodes that together hold the whole dataset. A datacenter is a logical grouping of nodes, often matching a physical region, used for replication and routing.
Because the ring is masterless, adding a node grows capacity linearly, and datacenters let you replicate across regions for locality and disaster recovery.
A consistency level (CL) is how many replicas must acknowledge a read or write before Cassandra calls it successful. You set it per query rather than globally, so you tune the consistency-versus-latency trade-off request by request instead of committing the whole system to one setting.
Common levels are ONE (one replica), QUORUM (a majority), and ALL (every replica). Lower levels are faster and more available; higher levels are more consistent but slower and less tolerant of node loss.
CONSISTENCY QUORUM;
SELECT * FROM events WHERE user_id = 5b5a...;Every node in a Cassandra ring is equal: there's no master, no config server, no single coordinator role that only one node can play. Any node can accept a read or write and route it to the right replicas.
Combined with replication, this means losing a node doesn't take the cluster down. Requests just go to another replica, which is why Cassandra is chosen for always-on systems.
Write-heavy, always-on workloads with known query patterns are the sweet spot: time-series and sensor data, event and activity logs, messaging and chat history, product catalogs, and recommendation or personalization feeds. Anything that ingests a lot of writes and must stay available across regions is a candidate.
It's a poor fit when you need joins, ad-hoc analytical queries, or strong transactions across many rows. If your access patterns change constantly or you can't predict queries in advance, a relational or document database usually fits better.
Key point: Pair every 'good fit' with a 'bad fit'. Naming where Cassandra is wrong is what separates understanding from a sales pitch.
cqlsh is the command-line shell that ships with Cassandra for running CQL interactively. You connect to a node and issue statements directly: create keyspaces, define tables, insert and query rows, and inspect the schema with DESCRIBE. It's built on the Python driver and talks to the cluster over the native protocol.
It's the fastest way to explore a cluster or prototype a data model, and it's what most people mean when they say they ran a query 'by hand' during an interview exercise.
cqlsh 127.0.0.1 9042
> DESCRIBE KEYSPACES;
> USE store;
> DESCRIBE TABLE events;Both exist, but in Cassandra they behave almost the same: an INSERT on an existing primary key overwrites, and an UPDATE on a missing key creates the row. Both are upserts. There's no separate 'row already exists' error like SQL.
This falls out of the write path: Cassandra just writes the new cell values with a timestamp, and the latest timestamp wins at read time. So INSERT and UPDATE are mostly a matter of which columns you're setting.
-- both create-or-overwrite the same row
INSERT INTO users (id, name) VALUES (1, 'Asha');
UPDATE users SET name = 'Asha K' WHERE id = 1;TTL (time to live) is a per-row or per-column expiry in seconds. After the TTL passes, the data automatically expires and is later removed during compaction. You set it on INSERT or UPDATE with USING TTL.
It's handy for data that should age out on its own: session tokens, short-lived caches, or recent-events tables. Expired data becomes a tombstone first, so very short TTLs on high-volume tables can pile up tombstones.
INSERT INTO sessions (id, token)
VALUES (7, 'abc')
USING TTL 3600; -- expires in one hourBecause Cassandra has no joins, you can't combine tables at read time. So you duplicate data into multiple tables, one shaped for each query you need to serve. Denormalization is how you make every read a single-partition lookup.
This means writing the same fact to several tables and keeping them in sync in your application. You trade extra writes and storage, which Cassandra handles cheaply, for fast reads, which is the whole point.
Key point: Say 'one table per query pattern' explicitly. That phrase is the mental model interviewers are checking for.
A static column stores one value shared across every row of a partition, rather than a value per clustering row. You mark it STATIC in the table definition. It's useful for partition-level metadata that shouldn't be duplicated on every row.
For example, in a table partitioned by account with one row per transaction, the account's name could be a static column: stored once for the partition, not repeated on each transaction row.
CREATE TABLE account_txns (
account_id uuid,
txn_id timeuuid,
owner_name text STATIC,
amount decimal,
PRIMARY KEY (account_id, txn_id)
);Cassandra has three collection types: set (unique, unordered values), list (ordered, allows duplicates), and map (key-value pairs). They let you store a small group of related values inside a single column without spinning up a separate table, which is handy for things like tags, emails, or preferences on one entity.
Keep collections small. They're read and written whole, and large collections hurt performance and can create tombstone problems. For anything that grows without bound, model it as clustering rows instead.
CREATE TABLE profiles (
id uuid PRIMARY KEY,
emails set<text>,
tags list<text>,
prefs map<text, text>
);Cassandra covers the everyday types you'd expect: text and varchar for strings, int, bigint, and decimal for numbers, boolean, timestamp and date for time, blob for binary, and uuid and timeuuid for identifiers. On top of those it adds the collection types set, list, and map.
timeuuid is worth knowing: it's a UUID that embeds a timestamp, so it's naturally time-sortable and makes a good clustering column for ordering events. Picking the right type up front avoids painful migrations later, since Cassandra can't freely alter a column's type.
CREATE TABLE orders (
id uuid PRIMARY KEY,
placed_at timestamp,
total decimal,
paid boolean,
event_id timeuuid
);For candidates with working experience: the write and read path, replication mechanics, and the modeling decisions that separate users from understanders.
A write first appends to the commit log on disk for durability, then updates the in-memory memtable. That's it for the client's latency; the write is acknowledged once enough replicas do this. The memtable later flushes to an immutable SSTable when it fills up.
Because writes are append-only (no read-before-write, no in-place update), Cassandra sustains very high write throughput. The cost is deferred: multiple SSTables accumulate and must be merged by compaction, and reads may touch several SSTables.
What happens on a write
No read happens on a normal write, which is why write throughput is so high.
Key point: Naming the commit log AND the memtable, in order, is the bar. Forgetting the commit log is the usual slip.
A read may need to merge data from the memtable and several SSTables, because a row's columns can live in different files written at different times. Cassandra checks the memtable, uses bloom filters to skip SSTables that can't contain the key, consults the key cache and partition index, then reads and merges the candidate SSTables.
It reconciles by cell timestamp: the newest value for each column wins, and tombstones mask deleted data. That reconciliation is why heavy tombstones or many SSTables slow reads down.
Key point: bloom filters in practice matters. It's the detail that separates readers from users.
The commit log is an append-only on-disk file that makes writes durable; it's replayed to rebuild memtables after a crash. The memtable is an in-memory, per-table write buffer. An SSTable (Sorted String Table) is an immutable on-disk file created when a memtable flushes.
Together they're the storage engine. Writes hit commit log plus memtable; memtables flush to SSTables; SSTables are never modified in place, only merged and replaced by compaction.
| Component | Location | Mutable? | Role |
|---|---|---|---|
| Commit log | Disk | Append-only | Durability, crash recovery |
| Memtable | Memory | Yes | Active write buffer |
| SSTable | Disk | No (immutable) | Persisted, merged by compaction |
Compaction is the background process that merges multiple SSTables into fewer ones, dropping tombstones past gc_grace_seconds and discarding cells that newer writes have superseded. It reclaims disk space and keeps reads fast by cutting down how many files a single read has to open and merge.
The main strategies fit different workloads: SizeTieredCompactionStrategy (STCS) is write-friendly and the default; LeveledCompactionStrategy (LCS) gives predictable read performance for read-heavy tables at higher write cost; TimeWindowCompactionStrategy (TWCS) suits time-series and TTL data.
| Strategy | Best for | Trade-off |
|---|---|---|
| STCS | Write-heavy, general use | Reads may hit many SSTables |
| LCS | Read-heavy tables | More write amplification |
| TWCS | Time-series, TTL data | Assumes time-ordered writes |
Key point: Matching a strategy to a workload (TWCS for time-series) is the answer that indicates production experience.
A tombstone is a marker Cassandra writes to record a deletion. Because SSTables are immutable, Cassandra can't erase a row in place; it writes a tombstone that masks the old data until compaction removes both, after gc_grace_seconds (10 days by default).
Tombstones become a problem when reads have to scan through many of them. Deleting lots of rows, inserting nulls, or expiring high-volume TTL data creates tombstone-heavy partitions that slow reads and can even fail queries past a threshold.
Key point: The gotcha the question needs: querying a range full of tombstones is slow even though the data is 'gone'. Say that.
Cassandra gives you strong consistency when the read replicas and write replicas are guaranteed to overlap: R + W > RF. The classic setting is QUORUM writes and QUORUM reads, since two majorities always share at least one node that has the latest write.
You can also trade: write ALL and read ONE, or write ONE and read ALL. QUORUM/QUORUM is the balanced default because it survives one replica being down while still overlapping.
RF = 3
QUORUM = floor(3/2) + 1 = 2
Write QUORUM (2) + Read QUORUM (2) = 4 > 3 -> overlap guaranteedKey point: Write the R + W > RF inequality. Interviewers light up when you show the math instead of just naming QUORUM.
SimpleStrategy places replicas around the ring without regard to datacenters or racks. It's fine for a single-datacenter test cluster and nothing else. NetworkTopologyStrategy places replicas per datacenter and spreads them across racks, so a rack or datacenter failure doesn't wipe out all replicas.
Production always uses NetworkTopologyStrategy, even with one datacenter, because it's rack-aware and it's the only sane path to multi-datacenter later.
ALTER KEYSPACE store WITH replication = {
'class': 'NetworkTopologyStrategy',
'dc1': 3,
'dc2': 3
};Gossip is how nodes share cluster state without a central coordinator. Every second, each node picks a few peers and exchanges what it knows about every node: which are up, their tokens, schema version, and load. State spreads through the ring in rounds, like rumor.
It's what makes the masterless design work. Nodes learn about failures and new members on their own, so there's no directory server that could be a single point of failure.
When a replica is down during a write, the coordinator stores a hint: a saved copy of the write plus where it belongs. When the downed node comes back within the hint window, the coordinator replays the hint so the node catches up on writes it missed.
It reduces the repair burden after short outages. It isn't a substitute for repair, though: hints expire, and if a node is down longer than the window, you still need read repair or anti-entropy repair to restore consistency.
Read repair fixes stale replicas during a read. When a read touches multiple replicas and finds mismatched values, Cassandra returns the newest (by timestamp) to the client and, in the background, writes the correct value back to the out-of-date replicas.
It's one of three anti-entropy mechanisms alongside hinted handoff and full repair. Read repair only fixes data that gets read, so cold data still relies on scheduled repair.
nodetool repair is the anti-entropy process that compares data across replicas using Merkle trees and then streams any differences so every replica converges on the same data. It's the safety net that catches the inconsistencies hinted handoff and read repair leave behind, especially on data that's rarely read.
You run it regularly, and specifically within gc_grace_seconds, so deletes propagate before tombstones are purged. Skipping repair is how deleted data 'comes back to life' when a replica that missed the tombstone gets read.
# repair a keyspace on this node
nodetool repair store
# check node and ring health
nodetool statusKey point: The 'deleted data resurrecting' story is the classic reason repair matters. Tell it and you've answered the follow-up.
A secondary index lets you query a non-primary-key column. Under the hood it's a hidden index table stored locally on each node, holding only that node's data. So a query by a secondary index has to fan out to potentially every node in the cluster.
Avoid them for high-cardinality columns (like an email, near-unique) and low-cardinality columns (like a boolean, huge partitions), and on large clusters where fan-out is expensive. Usually the better answer is a second table modeled for that query.
Key point: The strong answer ends with 'or model another table instead'. the question needs to see you reach for a table before an index.
A materialized view is a second table Cassandra builds and maintains automatically from a base table, keyed differently so it can serve a different query pattern. You only write to the base table, and Cassandra propagates each change into the view so the two stay in sync without any application code.
They save you from manually writing to a second denormalized table, but they've had consistency edge cases and carry a write cost, so they're marked experimental in many versions. Plenty of teams still hand-maintain denormalized tables for full control.
A counter is a special column type built for distributed increment and decrement, used for things like view counts, likes, or vote tallies. Instead of setting an absolute value, you update it with a delta, and Cassandra coordinates that change across replicas so concurrent increments from different clients don't clobber each other.
The limits are real: a counter table can only hold counter columns plus the primary key, counters can't be part of the primary key, updates aren't idempotent (a retried increment can double-count), and they're more expensive than normal writes. Use them only when you truly need distributed counting.
CREATE TABLE page_views (
page text PRIMARY KEY,
views counter
);
UPDATE page_views SET views = views + 1 WHERE page = 'home';A logged BATCH is for atomicity across a few writes that must all succeed or all fail together, usually updating several denormalized tables for one logical change, and ideally hitting a single partition. That's the right use: keeping copies of the same fact consistent, not squeezing out throughput.
The mistake is treating BATCH like a SQL bulk-insert for performance. Multi-partition batches make one coordinator do extra work and can hurt throughput and create hotspots. For loading many independent rows, send concurrent individual writes instead.
Key point: The trap is assuming BATCH speeds up bulk inserts. Saying it's for atomicity, not performance, is the whole point of the question.
Lightweight transactions add compare-and-set semantics using IF conditions, like INSERT ... IF NOT EXISTS or UPDATE ... IF column = value. They give you linearizable, one-partition conditional writes for cases like unique usernames or safe state transitions.
They cost a lot: LWT runs a Paxos consensus round, so it's several times slower than a normal write. Use them sparingly for genuine correctness needs, never as the default write path.
INSERT INTO users (email, id)
VALUES ('a@x.com', now())
IF NOT EXISTS; -- Paxos-backed conditional writeAim to keep partitions well under a few hundred megabytes and under about 100,000 rows as a rough ceiling. Cassandra reads and repairs at the partition level, so oversized partitions cause slow reads, GC pressure, and painful compaction.
You control size through the partition key. If a natural key (like a sensor id) would grow forever, add a time bucket to the partition key (sensor plus day) so each partition stays bounded.
Key point: The time-bucketing technique (bucket by day or month) is the fix interviewers hope you volunteer for unbounded partitions.
ALLOW FILTERING tells Cassandra to run a query it would otherwise reject because it can't answer it efficiently, typically a WHERE clause on non-primary-key columns. Cassandra then reads candidate rows and filters them in memory rather than jumping straight to the matching partition, which is exactly the work it normally avoids.
It's dangerous because it can scan huge amounts of data across the cluster, with latency that grows with the dataset. It's fine inside a single small partition, but as a way to query arbitrary columns at scale it's a red flag; model a new table instead.
Key point: Saying 'fine within one small partition, dangerous across the cluster' shows nuance. A flat 'never use it' misses the point.
Partition by the entity plus a time bucket, and cluster by timestamp descending. Bucketing (by hour, day, or month depending on volume) keeps partitions bounded, and DESC ordering returns the most recent readings first without a sort.
Pair this with TimeWindowCompactionStrategy and a TTL so old data ages out cleanly. This pattern (bucketed partition key, time-descending clustering, TWCS) is the canonical time-series model interviewers look for.
CREATE TABLE readings (
sensor_id uuid,
day date,
ts timestamp,
value double,
PRIMARY KEY ((sensor_id, day), ts)
) WITH CLUSTERING ORDER BY (ts DESC);A composite partition key groups two or more columns inside an extra set of parentheses in the PRIMARY KEY, so Cassandra hashes them together to pick a node. Without the inner parentheses, only the first column is the partition key and the rest become clustering columns.
You reach for it to bound partition size or to require both columns for a lookup. Bucketing a time-series table by (sensor_id, day) is the common case: it splits a sensor's endless history into daily partitions so no single partition grows without limit.
-- composite partition key: (sensor_id, day) hash together
PRIMARY KEY ((sensor_id, day), ts)
-- vs single partition key, day now clusters:
PRIMARY KEY (sensor_id, day, ts)Key point: The double-parentheses detail is the whole answer. Interviewers use this to check you know what actually partitions versus clusters.
advanced rounds probe internals, consistency guarantees, and production scars. Expect every answer here to draw a follow-up.
Cassandra hashes the partition key (Murmur3 by default) into a token, a point on a fixed ring of possible values. Each node owns ranges of that ring, so the token lands in exactly one node's range, and that node plus the next RF-1 nodes clockwise hold the replicas.
Modern clusters use virtual nodes (vnodes): each physical node owns many small, scattered token ranges instead of one big range. That spreads data and streaming more evenly and makes adding or removing nodes smoother.
Key point: Bringing up vnodes in practice signals real operational depth. Consistent hashing plus vnodes is the complete answer.
Cassandra is an AP system by default: during a network partition it favors availability and partition tolerance over consistency, so nodes keep serving reads and writes and reconcile the differences afterward. That's the deliberate design intent, inherited from Amazon's Dynamo, and it's why Cassandra stays up when parts of the cluster can't talk.
But it's tunable, not fixed. Per-query consistency levels let you shift toward consistency (QUORUM/QUORUM for R+W>RF), and LWT adds linearizable operations. So the honest answer is AP by default, with knobs to buy consistency where you need it, at a latency cost.
Key point: Say 'AP by default, tunable' rather than just 'AP'. The tunability is the nuance that separates senior from textbook answers.
Cassandra's storage engine is a log-structured merge tree. Writes are pure sequential appends (commit log plus memtable, then immutable SSTable flushes), never in-place updates. Sequential I/O is far faster than the random writes a B-tree does, which is where Cassandra's write throughput comes from.
The cost is on the read and maintenance side: a read may merge several SSTables, and background compaction has to keep merging files. B-trees, used by most relational databases, optimize the opposite way (fast in-place reads, slower random writes). Cassandra picks the write-optimized structure on purpose.
Key point: The trade-off framing (LSM = fast writes, compaction cost vs B-tree = fast reads, write cost) is what earns this. Don't just name LSM.
Watch a deeper explanation
Video: LSM Trees Explained: Powering Cassandra, RocksDB, HBase (CodeLucky, YouTube)
With NetworkTopologyStrategy you set a replication factor per datacenter, so each DC holds its own full set of replicas. Writes propagate across DCs asynchronously; the coordinator forwards to one node per remote DC, which fans out locally.
To keep reads local, use LOCAL_QUORUM (or LOCAL_ONE) so the query only waits on replicas in the client's datacenter, avoiding cross-DC latency. Application clients pin to their local DC via a datacenter-aware load-balancing policy.
Write: LOCAL_QUORUM -> acks from local DC, replicates cross-DC async
Read: LOCAL_QUORUM -> served entirely within the local DCKey point: LOCAL_QUORUM is the keyword. Naming it shows you've run Cassandra across regions rather than read about it.
That's the classic hot-partition or hot-token symptom: uneven load from a skewed partition key. nodetool status and nodetool tablestats to spot uneven load and large partitions, then find the offending partitions with tablehistograms and the logs' 'large partition' warnings comes first.
The fix is almost always the data model, not the cluster: repartition by adding a bucketing component to the partition key so a hot key's traffic and data spread across more partitions and nodes. Adding nodes alone won't fix a key that all traffic funnels into.
Key point: The senior tell is saying the fix is the schema, not more hardware. Interviewers wait to see if you reach for nodes or for the key.
gc_grace_seconds is how long a tombstone survives before compaction is allowed to purge it, 10 days by default. It exists to give repair time to propagate the deletion to every replica before the evidence of the delete disappears.
Set it too low, or skip repair inside the window, and deleted data resurrects: a replica that missed the tombstone still has the old value, and once the tombstone is gone, a read from that replica brings the 'deleted' row back. Set it very high and tombstones accumulate, slowing reads.
Key point: Data resurrection is the answer they want. gc_grace_seconds directly maps to running repair within the window.
Pick per operation based on what correctness the feature needs. Analytics or feeds tolerate ONE or LOCAL_ONE for lowest latency. User-visible reads and writes that must not lose data use LOCAL_QUORUM. Truly serial operations (unique constraints, state machines) use LWT.
Reason about it as R + W > RF for strong consistency, then relax where staleness is acceptable to buy latency and availability. Blanket ALL or blanket ONE across a system both signal you haven't thought per-workload.
An SSTable is a set of component files: Data (the actual rows), a partition Index, a Summary of that index held in memory, a bloom filter, Statistics, and a compression info file. Each cuts down the work a read does.
The bloom filter answers 'could this key be here?' cheaply so a read skips SSTables that can't hold the key. The summary and index then jump straight to the row's offset in the Data file instead of scanning. That's how a read stays fast even across many SSTables.
Full repair on a big cluster is heavy: it builds Merkle trees and streams data, competing with live traffic. So you use incremental repair (only un-repaired SSTables), subrange or primary-range repair to bound scope, and schedule during low-traffic windows, node by node.
In practice teams run a repair orchestrator (like Reaper) that segments and paces repairs across the ring, and they still target completion within gc_grace_seconds. The judgment being tested is that repair is mandatory but must be paced.
Watch for the SSTable count climbing, a backlog of pending compactions in nodetool compactionstats, and reads that touch many SSTables (visible in tablehistograms). Those signals mean compaction can't keep up with the write rate, which shows up to users as rising read latency and to operators as growing disk use.
Levers: match the strategy to the workload (LCS for read-heavy, TWCS for time-series), raise compaction throughput and concurrent compactors if hardware allows, and fix the upstream cause when it's really a modeling issue like tombstone floods or oversized partitions.
| Symptom | Likely cause | Lever |
|---|---|---|
| Rising SSTable count | Compaction behind on writes | More throughput/compactors |
| Reads hit many SSTables | Wrong strategy for read pattern | Switch to LCS |
| Tombstone warnings | Delete-heavy or TTL churn | Remodel, tune TWCS/gc_grace |
An LWT runs Paxos over the partition's replicas: a prepare/promise round, then a propose/accept round, then the actual write, with a read to check the condition. That consensus is what makes the conditional write linearizable within the partition.
The cost is roughly four round trips instead of one, so LWT is several times slower and contends badly under concurrency on the same partition. At scale you isolate LWT to the few operations that need it and design so hot keys don't funnel concurrent LWTs into one partition.
Key point: Quantifying the cost ('about four round trips, contention on hot keys') is what makes this The production-ready answer rather than a definition.
Adding a node: it bootstraps by claiming token ranges (many small ones with vnodes) and streaming the data for those ranges from existing replicas, then joins the ring and starts serving. Removing a node (decommission) streams its data to the nodes that will now own those ranges before it leaves.
Because vnodes scatter ranges, streaming spreads across many nodes rather than hammering one neighbor, so scaling is smoother. You run nodetool cleanup afterward on the remaining nodes to drop data they no longer own, and repair to be safe.
nodetool status # watch UN (up/normal) state
nodetool decommission # gracefully stream data off a node
nodetool cleanup # reclaim no-longer-owned dataStart from the application's queries, not the entities. List every read the app must serve, then design one table per query so each read is a single-partition lookup. Choose partition keys that spread load and keep partitions bounded, and clustering columns that match the required sort and range.
Then reconcile duplication: the same fact lives in several tables, so plan how writes keep them consistent. Finally validate partition sizes and access patterns against real cardinality before committing. Query-first, denormalize deliberately, bound partitions is the loop.
Query-first modeling loop
You loop this per access pattern; the same fact ending up in several tables is expected, not a smell.
Key point: the loop, especially 'start from queries' is the technical point. Modeling entities first (relational habit) is the fastest way to lose this one.
Backups use nodetool snapshot, which hard-links the current SSTables so the snapshot is cheap to take and consistent for that node at that moment. You then copy the snapshot files off-box for safekeeping, often alongside incremental backups that capture new SSTables as memtables flush between snapshots.
Restore means placing the SSTables back and loading them (sstableloader streams them respecting replication, or you restore files and refresh). Because each node holds only its ranges, a full restore coordinates across nodes, and you follow with repair to reconcile.
Cassandra runs on the JVM, and stop-the-world GC pauses directly become read and write latency spikes and, if long enough, make a node look dead to gossip and get marked down. Memtables, caches, and compaction all pressure the heap.
Tuning means choosing and sizing the collector (G1 for larger heaps is common), keeping heap sane rather than huge, and watching for pauses in the logs. Discord's well-known Cassandra pain was largely GC pauses on hot partitions, which is a good real example to cite.
Key point: Connecting GC pauses to a node being marked down by gossip shows you understand the failure mode, not just the tuning knob.
ScyllaDB reimplements the Cassandra model in C++ with a shard-per-core, shared-nothing design and no JVM, so it targets lower and more predictable tail latency and higher per-node throughput while staying wire-compatible with CQL. Cassandra's advantages are its mature ecosystem, wider deployment history, and no vendor tie.
You'd consider switching when JVM GC tail latency or per-node cost is the real bottleneck and the workload is latency-sensitive, since CQL compatibility keeps migration cost down. If you're not latency-bound, the switch rarely pays for itself.
| Cassandra | ScyllaDB | |
|---|---|---|
| Runtime | JVM (GC pauses possible) | C++, shard-per-core |
| Latency profile | Good, GC-sensitive tails | Lower, more predictable tails |
| Compatibility | Reference implementation | CQL wire-compatible |
| Ecosystem | Mature, broad | Smaller, growing |
Modeling like a relational database (normalizing then reaching for joins you don't have), unbounded partitions from a bad partition key, leaning on ALLOW FILTERING or high-cardinality secondary indexes instead of purpose-built tables, delete-heavy designs that flood tombstones, and giant multi-partition batches used as bulk inserts.
Underneath them all is one root cause: not designing around Cassandra's write path and query model. Every one of these shows up as latency or instability under production load, not in a small test.
Key point: Being able to rattle off anti-patterns AND their shared root cause (ignoring the write and query model) is the mark of production time.
When query patterns are unknown or change often, when the app needs joins and ad-hoc analytics, when strong multi-row transactions are core to the domain, or when the data volume simply doesn't justify a distributed database's operational cost. Small apps rarely need it.
Pushing back is itself a strong signal. The best engineers say Cassandra is a specialized tool for scale and availability, and reaching for it on a workload a single Postgres instance handles is a mistake that adds operational burden for no gain.
Key point: Interviewers plant this to see if you're a fan or an engineer. Recommending against your own topic when it fits shows judgment.
A bloom filter is a per-SSTable probabilistic structure that answers 'is this partition key definitely not here?'. It never gives a false negative, so a read can skip any SSTable the filter rules out, which is what keeps reads fast when a table has many SSTables.
bloom_filter_fp_chance sets the acceptable false-positive rate. Lower it and reads skip more SSTables but the filter uses more memory; raise it and you save memory at the cost of extra SSTable reads. You tune it per table based on read pattern and heap pressure.
ALTER TABLE events
WITH bloom_filter_fp_chance = 0.01; -- lower fp = fewer wasted SSTable readsKey point: The 'no false negatives' property is the point. Say why that lets a read safely skip an SSTable and you've nailed it.
A prepared statement is parsed and planned once on the cluster, then executed many times with bound values, which cuts per-query parsing cost and blocks CQL injection. For any query your app runs repeatedly, preparing it is the default best practice.
Token-aware routing lets the driver send a request straight to a replica that owns the data, instead of a random coordinator that then forwards it. That saves a network hop and load on coordinators. Together, prepared plus token-aware plus a datacenter-aware policy is the standard high-throughput client setup.
Key point: Naming token-aware routing shows you've built real clients, not just run cqlsh. It's the detail that separates app engineers from tinkerers.
Cassandra wins when you need very high write throughput, multi-datacenter replication, and no single point of failure, and you can model your tables around known query patterns. It trades away ad-hoc queries, joins, and strong-by-default consistency to get that. Where it loses is flexible querying and small-scale simplicity, which is where a document store or a relational database fits better. Knowing these trade-offs out loud is itself an interview signal: it shows you pick a database on merits, not habit.
| Database | Data model | Best at | Watch out for |
|---|---|---|---|
| Cassandra | Wide-column, masterless | High writes, multi-DC, always-on | No joins, query-first modeling only |
| MongoDB | Document, primary/replica | Flexible schemas, rich queries | Single primary for writes per shard |
| DynamoDB | Key-value / wide-column, managed | Serverless scale, no ops | AWS lock-in, cost at high throughput |
| PostgreSQL | Relational, single primary | Joins, transactions, ad-hoc SQL | Vertical scaling limits, write ceiling |
Prepare in layers, and practice out loud. Most Cassandra rounds move from concept questions to a data-modeling exercise to a design or debugging discussion, so rehearse each stage rather than only reading answers.
The typical Cassandra interview flow
Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.
10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.
Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Cassandra questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.
See how the AI Coding Interviewer works