Consumer API basics in Kafka - Time & Space Complexity
When using Kafka's Consumer API, it's important to know how the time to process messages changes as the number of messages grows.
We want to understand how the consumer's work scales when reading many messages.
Analyze the time complexity of the following Kafka consumer code snippet.
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("my-topic"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.println(record.value());
}
}
This code connects to Kafka, subscribes to a topic, and continuously polls for new messages, processing each one.
Look at what repeats as the consumer runs.
- Primary operation: Polling messages and iterating over each message in the batch.
- How many times: The poll loop runs indefinitely, and inside each poll, the for-loop runs once per message received.
As the number of messages increases, the consumer processes more messages each poll.
| Input Size (messages per poll) | Approx. Operations (prints) |
|---|---|
| 10 | 10 |
| 100 | 100 |
| 1000 | 1000 |
Pattern observation: The work grows directly with the number of messages received each time.
Time Complexity: O(n)
This means the time to process messages grows linearly with the number of messages received.
[X] Wrong: "Polling once will always take the same time regardless of messages."
[OK] Correct: The poll call returns a batch of messages, and processing each message takes time, so more messages mean more work.
Understanding how message processing time grows helps you design efficient consumers and shows you can reason about real-world streaming data.
"What if we processed messages in parallel inside the poll loop? How would that affect the time complexity?"
Practice
Solution
Step 1: Understand Kafka Consumer API role
The Consumer API is designed to read or consume messages from Kafka topics.Step 2: Differentiate from Producer API
The Producer API sends messages, while Consumer API reads them.Final Answer:
To read messages from Kafka topics -> Option DQuick Check:
Consumer API = Read messages [OK]
- Confusing Consumer API with Producer API
- Thinking Consumer API creates or deletes topics
- Mixing up reading and writing operations
Solution
Step 1: Recall Kafka consumer instantiation syntax
Kafka consumer is created by calling the constructor: new KafkaConsumer<>(properties);Step 2: Eliminate incorrect options
Producer is for sending messages, so new KafkaProducer<>(properties); is wrong. Options C and D are not valid KafkaConsumer methods.Final Answer:
new KafkaConsumer<>(properties); -> Option BQuick Check:
Constructor new KafkaConsumer<>(props) = Correct [OK]
- Using KafkaProducer instead of KafkaConsumer
- Calling non-existent static methods
- Confusing method names for creation
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("my-topic"));
ConsumerRecords records = consumer.poll(Duration.ofMillis(1000));
for (ConsumerRecord record : records) {
System.out.println(record.value());
}Solution
Step 1: Understand consumer subscription and poll
The consumer subscribes to "my-topic" and polls messages for 1 second.Step 2: Process messages in the topic
Since the topic has two messages "Hello" and "World", the poll will fetch both, and the loop prints each value.Final Answer:
Hello\nWorld -> Option CQuick Check:
poll() fetches messages = Hello and World printed [OK]
- Assuming poll returns no messages immediately
- Confusing subscribe with assign
- Expecting compile or runtime errors without cause
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "group1");
props.put("key.deserializer", "StringDeserializer");
props.put("value.deserializer", "StringDeserializer");
KafkaConsumer consumer = new KafkaConsumer<>(props);
consumer.subscribe(List.of("topic1"));
ConsumerRecords records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord record : records) {
System.out.println(record.value());
}Solution
Step 1: Check deserializer configuration
The deserializer class names must be fully qualified, e.g., "org.apache.kafka.common.serialization.StringDeserializer".Step 2: Identify impact of incorrect class names
Using "StringDeserializer" alone will cause ClassNotFoundException at runtime.Final Answer:
Deserializer class names are incomplete and will cause ClassNotFoundException -> Option AQuick Check:
Deserializer full class name required = Deserializer class names are incomplete and will cause ClassNotFoundException [OK]
- Using short class names for deserializers
- Ignoring missing consumer.close() (not error here)
- Assuming poll duration causes errors
Solution
Step 1: Disable auto commit for manual offset control
Setting "enable.auto.commit" to "false" allows manual offset commits.Step 2: Subscribe to multiple topics correctly
Use consumer.subscribe(Arrays.asList("topicA", "topicB")) to listen to multiple topics.Step 3: Commit offsets after processing
Calling consumer.commitSync() after processing ensures offsets are saved manually.Final Answer:
Code snippet with manual commit, multiple topic subscription, and commitSync() -> Option AQuick Check:
Manual commit + multiple topics = props.put("enable.auto.commit", "false"); consumer.subscribe(Arrays.asList("topicA", "topicB")); while (true) { ConsumerRecords records = consumer.poll(Duration.ofMillis(1000)); for (ConsumerRecord record : records) { process(record.value()); } consumer.commitSync(); } [OK]
- Using auto commit when manual commit is needed
- Mixing assign() and subscribe() incorrectly
- Forgetting to commit offsets after processing
Ready to prove it?
Pass the test and this pattern moves unknown
