Thinking clearly in interviews — Add Two Numbers
Understanding the why before the code (Rust · Python · Scala)
⚠️ Before we begin — a leadership lens worth following
Leadership in Change by Joel Salinas explores what it really means to lead through uncertainty, transformation, and complexity. It’s not motivational fluff - it’s grounded thinking about how leaders adapt, influence, and evolve when the environment shifts.
You’re standing at a small bakery counter with a friend. You each grab a few items, and the cashier reads out totals in a way that feels slightly… backward. Instead of “three dollars and forty-two cents”, you hear “two cents, then four dimes, then three dollars”. Your brain stalls for a beat. You know it’s the same amount, but it’s being presented from the “smallest” part first.
Nothing is broken. It’s just an order you don’t usually use.
Problem
You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.
You may assume the two numbers do not contain any leading zero, except the number 0 itself.
Examples
Example 1:
Input:
l1 = [2,4,3],l2 = [5,6,4]Output:[7,0,8]Explanation:342 + 465 = 807.Example 2:
Input:
l1 = [0],l2 = [0]Output:[0]Example 3:
Input:
l1 = [9,9,9,9,9,9,9],l2 = [9,9,9,9]Output:[8,9,9,9,0,0,0,1]Constraints
The number of nodes in each linked list is in the range
[1, 100].0 <= node value <= 9
It is guaranteed that the list represents a number that does not have leading zeros.
If you’ve ever counted out change from coins to bills, you’ve actually done this: you start with the smallest units, deal with carry (ten pennies make a dime), and only then move up. The process is natural. The representation is what’s unusual.
That tiny moment - “wait, what order am I seeing this in?” - is the entire heart of a famous coding interview problem. You’re given two numbers, but you don’t get them as normal integers. You get them as linked lists whose digits arrive least-significant first. Your job is to add them and return the result in the same “backward” form.
Interviewers like this because it’s not about advanced math. It’s about whether you can stay calm when the data format is unfamiliar, translate it into a process you already understand (grade-school addition), and implement it cleanly without tripping over edge cases. Under time pressure, the winner isn’t the person who’s seen the trick - it’s the person who can recognize that there isn’t a trick.
🎙️ Why interviewers like this problem
From an interviewer’s perspective, Add Two Numbers is a compact test of a lot of real engineering skills.
First, it tests whether you can map a concept to a representation. Adding numbers is easy; adding numbers when the digits are stored in a linked list - reversed - is where candidates reveal how quickly they can normalize a problem. Strong candidates don’t complain about the format; they convert it into a mental model (“this is just digit-by-digit addition with carry”).
Second, it exposes your relationship with pointers/references and incremental construction. Linked lists are intentionally a little awkward: you can’t index, you can’t easily go backward, and you need to build results node by node. The problem forces you into iterative thinking, careful state management, and clean handling of “end of list” conditions.
Third, it’s a communication problem disguised as an algorithm problem. In a live interview, you’re expected to narrate your reasoning: what variables you’re tracking, why carry matters, what happens when one list is longer, and what you do at the end if there’s still carry left.
Finally, it’s a safe place to evaluate code quality. There’s no need for fancy optimizations, but there’s plenty of room for messy code: off-by-one mistakes, null handling bugs, unnecessary conversions, and overly clever approaches that hide correctness issues. Interviewers want to see whether you can write code that is both correct and readable when you’re being watched.
📖 Understanding the problem in plain language
You’re given two singly linked lists. Each node holds one digit (0–9). If you read the list from head to tail, you’re reading the number from least significant digit to most significant digit.
So the list [2, 4, 3] represents the number 342, because:
head
2is the ones placethen
4is the tens placethen
3is the hundreds place
You need to add the two numbers represented by the two lists and return a new linked list representing their sum in the same reversed-digit format.
A few important clarifications that are easy to miss when you’re nervous:
The lists are non-empty. You’ll always have at least one node in each.
Digits are non-negative single digits. No node contains
12or-1.The numbers have no leading zeros in the normal representation, but that’s not the main challenge here. (And
0itself is allowed.)The result can be longer than either input list, because addition can introduce a new most-significant digit (like
999 + 1 = 1000).
The most important mental shift is this: you do not need to convert the lists to integers. In fact, you usually shouldn’t. The natural solution is to simulate grade-school addition as you walk the lists.
👣 Reasoning through examples
Let’s walk through the classic example slowly, the way you’d narrate it in an interview.
l1 = [2, 4, 3] represents 342l2 = [5, 6, 4] represents 465
We add digit by digit, starting from the head (ones place), carrying when needed.
Start with
carry = 0.Ones place:
2 + 5 + 0 = 7→ write down 7, carry 0.Tens place:
4 + 6 + 0 = 10→ write down 0, carry 1.Hundreds place:
3 + 4 + 1 = 8→ write down 8, carry 0.Done,
carry is 0, so stop.
Result: [7, 0, 8] which represents 807. That matches what we expect.
Now look at an example where carry survives past the last digits:
l1 = [9, 9, 9, 9, 9, 9, 9]l2 = [9, 9, 9, 9]
Here’s how you should describe your thinking out loud:
“I’m going to traverse both lists until both are exhausted and there’s no carry.”
“At each position, if a list is exhausted, I treat its digit as 0.”
“I compute sum = digit1 + digit2 + carry, output sum % 10, and update carry = sum // 10.”
If you narrate it like that, you’ll naturally handle unequal lengths and the final carry without special-case hacks.
Where intuition often fails: candidates sometimes stop when both pointers are null and forget about a remaining carry. Or they try to handle carry with complicated branching instead of one consistent formula. The formula is your anchor under pressure.
📏 Constraints and what they tell us
The lists have between 1 and 100 nodes each. That’s small, but don’t let it trick you into sloppy thinking.
In interviews, constraints are less about “do we need an O(n log n) trick?” and more about “what approaches are appropriate and safe?”
Here’s what the constraints imply:
Time complexity: a single pass over both lists is obviously fine. The natural solution is
O(max(m, n)).Space complexity: you need a result list, so
O(max(m, n))additional nodes is unavoidable. Beyond that, you should beO(1)extra space.Avoid integer conversion: even though 100 digits could fit in big integers in some languages, converting is usually considered a red flag in interviews. It dodges the linked-list aspect and can introduce overflow issues in languages without arbitrary precision.
Input size isn’t huge, but correctness still matters: 100 nodes is enough to expose bugs in carry logic, termination conditions, and list construction.
Interviewers also use these constraints as a gentle prompt: “This is meant to be straightforward”. If you find yourself designing stacks, recursion, or complex data structures, pause and ask whether you’re solving a different problem than the one you were given.
🤔 The obvious first idea — and why it’s not enough
The most common first idea is: “Convert each linked list into an integer, add them, then convert back”.
It feels natural because that’s how humans add numbers: we think of whole numbers, not lists of digits. And in a scripting language with big integers, it might even work for these constraints.
But in interviews, this approach is usually not enough for three reasons:
First, it ignores what’s being tested. The problem is deliberately built around linked list traversal, incremental output construction, and carry handling. If you convert to integers, you’re side-stepping the core skill signal.
Second, it can be unsafe or awkward across languages. In many environments (or in lower-level languages), integers have fixed size. A 100-digit number will overflow any standard 64-bit type. You could use big integer libraries, but now your “simple” solution depends on library availability and adds noise to the interview.
Third, conversion introduces its own pitfalls: building strings, reversing, parsing, and then reconstructing the list. You end up with more code, more failure modes, and less clarity.
A strong candidate will often acknowledge the naive approach quickly - “We could convert, but that risks overflow and misses the point” - and then pivot to the real approach: simulate addition directly on the lists.
That pivot is an interview moment. It shows you can evaluate trade-offs rather than blindly implementing the first thought.
💡 The key insight
The key insight is that the “reverse order” representation is not a complication - it’s a gift.
If digits were stored most-significant first, you’d have a real problem: addition starts from the least-significant digit, so you’d need to reverse lists, use stacks, or recurse to reach the tail first.
But this problem hands you the digits starting from the least-significant place. That means you can add as you traverse, exactly like counting change from coins upward. No lookahead, no backtracking.
Once you accept that, the algorithm becomes almost mechanical:
Maintain a
carry.Walk both lists in lockstep.
At each position, read the current digit from each list (or 0 if that list is finished).
Compute
sum = d1 + d2 + carry.Output
sum % 10.Update
carry = sum // 10.Continue until both lists are finished and
carry == 0.
This insight also tells you how to structure your code cleanly: one loop, one consistent update rule, and a dummy head node to simplify list construction.
In interviews, “dummy head” is often a differentiator. It’s not advanced, but it signals you’ve built lists before and you know how to avoid special-casing the first node.
💡 Did this mental shift click for you?
What was your “aha” moment while reading this?
💬 Leave a comment - I’d love to know how you think about Add Two Numbers.
⚙️ The final approach
Narratively, here’s the approach you want to be able to explain smoothly:
You create a new linked list for the result. You keep a pointer (tail) to the last node in the result so you can append efficiently. You traverse l1 and l2 with pointers p and q.
At each iteration:
Let
xbep.valifpexists, otherwise 0.Let
ybeq.valifqexists, otherwise 0.Let
s = x + y + carry.The digit to store is
s % 10.The next carry is
s / 10(integer division).
Append the digit to the result, move p and q forward if they exist, and repeat.
Correctness argument (the kind interviewers like):
At each step you compute the correct digit for that place value because you include the carry from the previous place.
Carry is always 0 or 1 (since max digit sum is
9 + 9 + 1 = 19), so it’s safe and bounded.You terminate only when there are no digits left in either input and no carry left to propagate, ensuring the most-significant carry is not lost.
Edge cases are naturally handled:
One list longer than the other → missing digits treated as 0.
Final carry after both lists end → loop continues and appends it.
Inputs are
[0]and[0]→ result becomes[0].
This is the kind of solution that stays stable under pressure because it has one moving part: the carry and the loop.
⏱️ Performance under interview constraints
Time complexity is O(max(m, n)). You touch each node in each list once, and do O(1) work per node.
Space complexity is O(max(m, n)) for the output list. Extra working space (not counting the output) is O(1): a few pointers and integers.
In a live interview, you should justify complexity in plain language:
“We traverse both lists once, so runtime is linear in the length of the longer list.”
“We allocate one node per digit in the result, which is at most one more than the longer input due to carry.”
Trade-offs are minimal here, but you can still show maturity by stating what you’re not doing:
“I’m not converting to integers to avoid overflow and unnecessary work.”
“I’m not using recursion because iteration is simpler and avoids stack depth concerns.”
That’s often enough to signal strong judgment without over-explaining.
💻 Implementation
Implementation is secondary to clarity of thought, but in interviews, correctness and readability are non-negotiable. The easiest way to write clean code here is:
Use a dummy head node.
Keep a
tailpointer for appending.Use a loop condition that includes carry:
while p != null or q != null or carry != 0.
Also, keep your variable names boring. “carry”, “dummy”, “tail”, “p”, “q” are boring in a good way: they reduce cognitive load for both you and the interviewer.
⚠️ Know someone preparing for interviews?
Send this to them - it might save them from an avoidable mistake.
🔁 Share this post with a friend grinding interview prep.
🐍 Python implementation
In Python, the implementation reads very close to the algorithm. The main detail is defining a ListNode class and carefully moving pointers.
from __future__ import annotations
from typing import Optional
class ListNode:
def __init__(self, val: int = 0, next: Optional[”ListNode”] = None):
self.val = val
self.next = next
def addTwoNumbers(l1: ListNode, l2: ListNode) -> ListNode:
dummy = ListNode(0)
tail = dummy
carry = 0
p, q = l1, l2
while p is not None or q is not None or carry != 0:
x = p.val if p is not None else 0
y = q.val if q is not None else 0
s = x + y + carry
digit = s % 10
carry = s // 10
tail.next = ListNode(digit)
tail = tail.next
if p is not None:
p = p.next
if q is not None:
q = q.next
return dummy.nextA couple of interview-friendly notes you can say while writing:
“The dummy node avoids a special case for the head of the result list.”
“The loop condition includes carry so we don’t drop a final carry digit.”
“When one list ends, I treat its digit as 0.”
If you want to be extra careful, you can mention that the algorithm supports [0] + [0] naturally, and that digits are guaranteed 0–9 so carry stays bounded.
🦀 Rust implementation
Rust adds two kinds of pressure: ownership and the Option<Box<ListNode>> shape. The trick is not to fight it. Use references to traverse (as_ref()), and build the output list with a mutable tail pointer.
Below is a common, readable pattern: we keep p and q as Option<&Box<ListNode>> (borrowed traversal), while we build the result list as owned nodes.
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ListNode {
pub val: i32,
pub next: Option<Box<ListNode>>,
}
impl ListNode {
#[inline]
pub fn new(val: i32) -> Self {
ListNode { val, next: None }
}
}
pub fn add_two_numbers(
l1: Option<Box<ListNode>>,
l2: Option<Box<ListNode>>,
) -> Option<Box<ListNode>> {
let mut carry: i32 = 0;
// Borrowed pointers for traversal (we don’t need to mutate inputs).
let mut p = l1.as_ref();
let mut q = l2.as_ref();
// Dummy head for output.
let mut dummy = Box::new(ListNode::new(0));
let mut tail = &mut dummy;
while p.is_some() || q.is_some() || carry != 0 {
let x = p.map(|n| n.val).unwrap_or(0);
let y = q.map(|n| n.val).unwrap_or(0);
let s = x + y + carry;
let digit = s % 10;
carry = s / 10;
tail.next = Some(Box::new(ListNode::new(digit)));
tail = tail.next.as_mut().unwrap();
p = p.and_then(|n| n.next.as_ref());
q = q.and_then(|n| n.next.as_ref());
}
dummy.next
}What to point out in an interview:
We traverse by borrowing (
as_ref()), which avoids moving ownership out ofl1/l2during the loop.We use a dummy head and a mutable reference
tailto append inO(1).tail.next.as_mut().unwrap()is safe here because we just settail.nexttoSome(...).
If the interviewer asks about safety: integer operations are safe here because values are tiny (0..9 plus carry), so no overflow concerns with i32.
🟣 Scala implementation
In Scala, you typically model a linked list node with a class containing var next so you can build incrementally. The core logic remains the same: traverse, compute digit and carry, append.
final class ListNode(var value: Int, var next: ListNode = null)
object Solution {
def addTwoNumbers(l1: ListNode, l2: ListNode): ListNode = {
val dummy = new ListNode(0)
var tail = dummy
var carry = 0
var p = l1
var q = l2
while (p != null || q != null || carry != 0) {
val x = if (p != null) p.value else 0
val y = if (q != null) q.value else 0
val s = x + y + carry
val digit = s % 10
carry = s / 10
tail.next = new ListNode(digit)
tail = tail.next
if (p != null) p = p.next
if (q != null) q = q.next
}
dummy.next
}
}Design choices worth mentioning:
This is intentionally iterative: it’s simpler and avoids recursion depth concerns.
dummyandtailkeep the append logic clean.Using
nullfor list termination is common when mirroring interview-style list node definitions; in production Scala you might preferOption, but that adds verbosity that often isn’t helpful in a timed setting.
⚠️ Common interview mistakes
People rarely fail this problem because they don’t know how to add numbers. They fail because they lose control of the small moving parts while talking and coding at the same time.
Forgetting the final carry is the classic one. You get a correct-looking list for many inputs, but you break on things like [5] + [5] (should produce [0, 1]). The cure is structural: bake carry into the loop condition. Don’t try to “remember” to handle it afterward.
Mismanaging list traversal is another. Candidates sometimes advance pointers too early, or they read p.val without checking p != null. Again, the cure is to adopt a stable pattern: read digits with conditional expressions, then advance pointers at the end of the loop.
Building the result list with special cases is also common: “If head is null set head, else append…”. Under pressure, that’s where bugs breed. Dummy head is the antidote because it makes every append identical.
Finally, over-complicating the solution can be its own failure mode. If you find yourself reversing lists, creating stacks, or converting to strings, pause. Ask: “Am I still doing digit-by-digit addition?” If not, you’re probably leaving the clean path.
🔍 Edge cases interviewers love to ask about
Interviewers often probe edge cases not to be tricky, but to see whether your solution is robust and whether you can reason about it calmly.
One list is much longer than the other is a favorite. For example: [1] + [9,9,9]. The correct behavior is to keep going after the shorter list ends and treat its digits as 0. Your loop condition and digit extraction should already handle this.
A final carry that extends the result is another standard check: [9,9] + [1] should yield [0,0,1]. When asked, you want to respond with confidence: “My loop continues while carry is non-zero, so it appends an extra node when needed.”
Inputs containing zeros are also asked, especially [0] + [0]. Some buggy solutions accidentally return an empty list or skip node creation. With the dummy-head approach, this case is naturally fine.
Occasionally, an interviewer asks about mutating inputs: “Can you reuse nodes?” In most interview settings, returning a new list is the expected approach. If asked, you can say: “We could reuse nodes to reduce allocations, but it complicates the code and risks modifying inputs unexpectedly; I’d stick with a fresh list unless explicitly asked.”
And sometimes they ask the “what if” variant: digits stored in forward order. The correct response is not to panic - it’s to identify the difference: forward order prevents left-to-right addition, so you’d need to reverse lists, use stacks, or use recursion. The important part is that you can articulate the dependency: addition wants least-significant digits first.
🎓 What this problem trains you to do
This problem trains a very transferable interview skill: staying oriented when the representation is unfamiliar.
In real engineering, data is rarely handed to you in the ideal shape. It’s serialized, chunked, streamed, reversed, sparse, or wrapped in an API that forces a certain access pattern. The winning move is often to stop thinking “this is weird” and start thinking “what process does this representation make natural?”
Here, reverse-order digits make carry-based addition natural. You don’t need to fight the structure. You lean into it.
It also trains incremental construction and pointer discipline - skills that show up in many classic tasks: merging sorted lists, removing nodes, partitioning lists, building trees iteratively, and streaming algorithms where you produce output as you consume input.
Most importantly, it trains a style of reasoning that interviewers trust: a stable loop invariant (“I’ve computed all digits up to this position correctly, carry represents overflow to the next position”), a simple termination condition, and small, repeatable operations.
When you can explain that clearly, you’re not just “solving the problem”. You’re demonstrating that you can think like an engineer under observation.
🌱 Closing thoughts
There’s a quiet confidence that comes from problems like this - not because they’re hard, but because they reward steadiness. The representation is a little unusual, and that’s enough to trigger doubt in the moment. The way through is to recognize the familiar process hiding underneath: add digits, carry, repeat.
If you practice explaining this solution out loud - carry logic, loop condition, why dummy head simplifies construction - you’ll find that the code almost writes itself. And that’s the real goal for live interviews: reduce the number of decisions you have to make while someone is watching.
🧠 If you want to build real interview intuition…
I write deep, calm breakdowns of classic problems - without tricks or memorization.
📬 Subscribe to get the next one directly.









