Spark Streaming vs Structured Streaming
DStreams Compared
Spark Streaming (DStreams) is the original RDD-based micro-batch API, and Spark's documentation calls it a legacy project with no more updates. Structured Streaming is the current engine: you write a DataFrame query and the Spark SQL engine runs it incrementally. It supports event-time windows and watermarks, and it gives end-to-end exactly-once for replayable sources and idempotent sinks.
Spark Streaming vs Structured Streaming in short
Spark Streaming, also called DStreams, is the first streaming API in Spark: it cuts a stream into small batches and hands you each one as an RDD. Structured Streaming is its replacement. You write a DataFrame query as if the data were a static table, and the Spark SQL engine runs it incrementally as new rows arrive.
The Spark streaming programming guide calls Spark Streaming the previous generation of the engine, says there are no longer updates to it and calls it a legacy project. It tells you to use Structured Streaming for your streaming applications and pipelines. For a new job the choice is made. The differences still matter if you maintain a DStreams job or plan to migrate one, and an interviewer may ask about them.
DStreams vs Structured Streaming at a glance
- Core abstraction
- You write
- Windows are set in
- Delivery
- Time between runs
- Status in the docs
- Core abstractionDStream of RDDs
- You writeRDD operations
- Windows are set inBatch interval multiples
- DeliveryAt-least-once output
- Time between runsBatch interval
- Status in the docsLegacy, no updates
- Core abstractionUnbounded DataFrame
- You writeDataFrame and SQL
- Windows are set inEvent time
- DeliveryExactly-once, replayable
- Time between runsTrigger
- Status in the docsCurrent engine
How DStreams and Structured Streaming model a stream
A DStream is a sequence of RDDs. The batch interval you pass to StreamingContext decides how much of the stream goes into each RDD, and every operation you write is a transformation on those RDDs. With a 2 second interval, your code sees 1 RDD every 2 seconds and has no table to query.


Structured Streaming treats the live stream as a table that is continuously appended. The query you write on it is the same as on a static DataFrame, and Spark runs it as an incremental query on the unbounded input table. The guide notes that Structured Streaming doesn't materialize the entire table. It reads the latest available data and updates the result, then discards the source data. Each trigger appends new rows to the input table and updates the result table.
The practical consequence is where the optimizer sits. A DStream program is a chain of RDD functions that Spark runs as you wrote them. A Structured Streaming query goes through the same optimized Spark SQL engine as a batch DataFrame job.
The same word count in DStreams and Structured Streaming
from pyspark import SparkContext
from pyspark.streaming import StreamingContext
sc = SparkContext("local[2]", "NetworkWordCount")
ssc = StreamingContext(sc, 2) # batch interval in seconds
lines = ssc.socketTextStream("localhost", 9999)
words = lines.flatMap(lambda line: line.split(" "))
pairs = words.map(lambda word: (word, 1))
counts = pairs.reduceByKey(lambda x, y: x + y) # counts per batch only
counts.pprint()
ssc.start()
ssc.awaitTermination()from pyspark.sql import SparkSession
from pyspark.sql.functions import explode, split
spark = SparkSession.builder.appName("StructuredNetworkWordCount").getOrCreate()
lines = (spark.readStream.format("socket")
.option("host", "localhost").option("port", 9999).load())
words = lines.select(explode(split(lines.value, " ")).alias("word"))
counts = words.groupBy("word").count() # running count across all data
query = counts.writeStream.outputMode("complete").format("console").start()
query.awaitTermination()State, aggregations and joins
The 2 word count listings look alike but count different things. reduceByKey on a DStream counts within each batch. A running total across batches needs updateStateByKey, which requires checkpointing, runs its update function for every key in every batch and drops a key when the function returns None. In Structured Streaming groupBy().count() is already a running count, and Spark keeps its state in the checkpoint location.
For custom per-key state, Structured Streaming has mapGroupsWithState and flatMapGroupsWithState since Spark 2.2. Since Spark 4.0 the guide encourages the newer transformWithState operator for complex stateful applications. Stream-stream joins exist since Spark 2.3, and for outer joins you must specify a watermark and event-time constraints.
Structured Streaming also has limits you will hit. On streaming Datasets, limit isn't supported, nor is taking the first N rows, and distinct isn't supported either. Sorting works only after an aggregation in Complete output mode. Actions such as count() and show() do not apply: you write to a sink, such as the console sink, instead.
Know Spark Streaming vs Structured Streaming the way the interviewer who asks it knows it.
Event time, windows and late data
A DStream window is defined by a window length and a slide interval, and both must be multiples of the batch interval. Windows are cut from when batches were processed. Event time is the time embedded in the data itself, and in Structured Streaming a window aggregation is a grouping on the event-time column, where each window is a group and a row can belong to several windows.
Late data is where that matters. Since Spark 2.1 Structured Streaming supports watermarks, which let you state how late data may be and let the engine clean up old state. The guarantee runs in one direction: data less than the threshold behind the latest event time is guaranteed to be aggregated, and data later than that may or may not be.
How a watermark closes a window
Delivery guarantees: exactly-once in each API
Both APIs can reach exactly-once, by different routes. With DStreams, the guarantee depends on the source and the output. Reliable receivers acknowledge a source only after the data is replicated, unreliable receivers can lose data on failure, and the Kafka Direct API, introduced in Spark 1.3, receives Kafka data exactly once. Output operations such as foreachRDD are at-least-once, so exactly-once end to end needs an idempotent update or a transaction identified by batch time and partition index.
Structured Streaming builds the guarantee into the engine. Sources are assumed to have offsets, the engine records the offset range of each trigger with checkpointing and write-ahead logs, and the sinks are designed to be idempotent for reprocessing. With replayable sources and idempotent sinks, the Structured Streaming guide states end-to-end exactly-once semantics under any failure. The exception you own is foreachBatch, which is at-least-once by default: use the batchId it passes you to deduplicate.
Batch interval vs trigger, and how fast each can go
A DStream job is stable only while batch processing time stays below the batch interval. Take a 2 second interval and batches that need 3 seconds: each batch finishes 1 second later than the next one is due, so after 10 batches the job is 10 seconds behind and still falling back.
Structured Streaming handles overruns by rule. With no trigger set, the next micro-batch starts as soon as the previous one completes. With a fixed interval, if a batch overruns, the next starts immediately instead of waiting for the next boundary, and if no new data is available no batch runs. The default micro-batch engine reaches end-to-end latencies of about 100 ms at best. Continuous Processing, experimental since Spark 2.3, aims at about 1 ms with at-least-once guarantees for map-like queries. Real-time Mode, introduced in Spark 4.1.0, targets millisecond latency with the same APIs, for stateless queries in 4.1.0 and some stateful ones from 4.3.0.
A trigger that DStreams never had is availableNow: it processes everything available, possibly in several micro-batches, then stops. It suits jobs that start a cluster periodically, process what arrived since the last run and shut down. The older one-time trigger is deprecated in its favor.
Moving from DStreams to Structured Streaming
| DStreams piece | Structured Streaming replacement | What changes |
|---|---|---|
| `StreamingContext` | `SparkSession.readStream` | The entry point is the SparkSession you already use for batch. |
| Batch interval | Trigger | Run as fast as possible, at a fixed interval, or `availableNow`. |
| RDD `map` and `reduceByKey` | DataFrame `select` and `groupBy` | Logic is rewritten against columns and goes through the SQL engine. |
| `window` on batch intervals | `window` on an event-time column | Windows follow the time in the data and can use a watermark. |
| `updateStateByKey` | Aggregation or `transformWithState` | State lives in the checkpoint location, and you set its cleanup with a watermark. |
| `foreachRDD` | `foreachBatch` or a sink | Writes are at-least-once, so deduplicate with the batch id. |
| Checkpoint directory | `checkpointLocation` option | Set per query, on an HDFS-compatible fault-tolerant file system. |
What to check in an existing DStreams job
A DStreams job in production usually carries more than a chain of transformations. Start with the state: every updateStateByKey or windowed reduce with an inverse function depends on checkpointing, and each one needs a Structured Streaming aggregation or stateful operator with a watermark that bounds how long state lives.
Then read the output. foreachRDD is at-least-once, so the job already carries an idempotent write or a transactional commit tagged with batch time and partition index. In Structured Streaming the equivalent is the built-in idempotent sinks, or foreachBatch with the batchId as the deduplication key. Last, find the clocks. A DStreams window counts time by when batches ran, so a rewrite to event-time windows changes which window a late record lands in, and the new output should be compared with the old before the old job is switched off.
How this comparison comes up in data engineering interviews
Interviewers use the pair to see whether you know what a streaming engine guarantees. The strong answer names the model (RDD micro-batches vs an incrementally maintained query), says where event time and watermarks come in, and says what you must add yourself for exactly-once output. Be ready to say which trigger fits a job and what happens when a batch overruns.
Practice the Spark side with the PySpark interview questions and the PySpark practice problems. The design side, such as choosing batch or streaming for a pipeline, is covered by streaming system design interview questions and the data engineer interview prep hub.
Spark Streaming vs Structured Streaming FAQ
What is the difference between Spark Streaming and Structured Streaming?+
What is a DStream?+
Is Spark Streaming deprecated?+
Is Structured Streaming real streaming or micro-batch?+
Does Structured Streaming support exactly-once processing?+
Can I use the same code for batch and streaming?+
Should I migrate DStreams jobs to Structured Streaming?+
The candidate who gets the offer
- 01
Reading a solution is not the same as writing one
Every engineer who has frozen on a query they had read a dozen times knows the gap. The only preparation that closes it is producing the answer yourself, under time, before the interview does it for you
- 02
76% of hiring managers reject on the coding task, not the resume
From HackerRank's 2024 Developer Skills Report. Candidates who look strong on paper still fail the live screen if they haven't done timed, executable practice
- 03
Spark rounds are diagnosis rounds
Skew, shuffles, broadcast judgment, partition pruning, exactly-once streaming. Reading the task table and naming the failure class is the signal, not the API
Related guides
PySpark interview questions
Spark questions asked in data engineering interviews
PySpark practice problems
Hands-on Spark problems to practice before the loop
Streaming system design interview questions
Design questions on streaming pipelines
Data engineer interview prep
The full preparation path for the loop
This week's community challenge
Dirty, production-shaped data, scored blind each week.