Data types & variables: interview questions & scenarios
These are the data-type questions I see most in Java screens and first-round interviews. Most of them are "what does this print?", because three lines of arithmetic or boxing are the fastest way for an interviewer to see whether you know the rules or are guessing.
Here's how to use this page: read the question and the code, say your answer out loud first, then read the model answer. The "What if..." follow-ups are the ones interviewers actually ask next, so don't skip them. If a question stumps you, the matching lesson is in this topic: primitives, wrappers, casting, defaults and constants and var.
What does this print: Integer a = 127, b = 127, c = 128, d = 128; then a == b, c == d and c.equals(d)?
Tests autoboxing, Integer cache, identity vs equality
Show answerHide answer
Model answer
Integer a = 127, b = 127, c = 128, d = 128;
System.out.println(a == b); // true
System.out.println(c == d); // false
System.out.println(c.equals(d)); // true
Autoboxing compiles to Integer.valueOf(int), which returns cached instances for -128 to 127. So a and b are the same object and == (a reference comparison) is true. 128 is outside the cache, so c and d are two different objects: == is false, while equals() compares values and is true.
How I’d say it in the interview
"True, false, true. Boxing goes through Integer.valueOf, which caches -128 to 127, so the two 127s are literally the same object. The 128s are separate objects, so == fails. That's why I always compare wrappers with equals."
Signals depth
Saying where the cache lives (Integer.IntegerCache, used by valueOf), that the upper bound is configurable, and that Double and Float have no cache at all.
Red flags
"== compares values for numbers" or "it's random". Also: suggesting new Integer(...) as a fix (deprecated for removal since Java 16).
Follow-ups interviewers ask next
What if we compare
c == 128with a primitive instead?It's
true. When one operand is a primitiveint, theIntegeris unboxed and the comparison is numeric.What if we start the JVM with
-XX:AutoBoxCacheMax=1000?Then
Integer.valueOf(128)comes from the cache too, andc == dprintstrue. Only theIntegerupper bound is configurable; the lower bound stays -128, andLong,ShortandCharactercaches are fixed. Code that depends on this is still a bug.What if it's
Long x = 127L, y = 127L;and we callx.equals(127)?false. The argument127boxes to anInteger, andLong.equalsreturnsfalsefor anything that isn't aLong.x.equals(127L)istrue.
What does System.out.println(0.1 + 0.2 == 0.3) print, and how should you compare doubles?
Tests IEEE 754, floating-point comparison
Show answerHide answer
Model answer
It prints false; 0.1 + 0.2 is 0.30000000000000004. double is IEEE 754 binary floating point, and 0.1, 0.2 and 0.3 have no exact binary representation, so each is stored as the nearest representable value and the rounding errors don't cancel.
Compare with a tolerance, Math.abs(a - b) < 1e-9, scaled to your data. For money or anything that must be exact in decimal, use long minor units (cents, paise) or BigDecimal with an explicit RoundingMode.
How I’d say it in the interview
"False, because doubles are binary fractions and 0.1 can't be represented exactly. I compare doubles with an epsilon, and for money I don't use doubles at all: long cents or BigDecimal."
Signals depth
Mentioning that new BigDecimal(0.1) captures the binary error while BigDecimal.valueOf(0.1) or new BigDecimal("0.1") doesn't.
Red flags
"Java has a bug in addition", or recommending float for "more precision".
Follow-ups interviewers ask next
What if I ask what
Double.NaN == Double.NaNprints?false. NaN is unequal to everything, including itself, under IEEE 754. UseDouble.isNaN(x). Note thatDouble.valueOf(Double.NaN).equals(Double.NaN)istrue, becauseequalscompares bit patterns so NaN can work as a map key.What if I ask about
0.0 == -0.0?truewith==, butDouble.compare(0.0, -0.0)is1andequalsreturnsfalse. The two zeros are different bit patterns, and1 / -0.0is-Infinity.
Why does byte b = 10; b = b + 1; fail to compile, while b += 1; works?
Tests binary numeric promotion, compound assignment
Show answerHide answer
Model answer
Java never does arithmetic in byte, short or char. Binary numeric promotion (JLS §5.6) turns both operands of + into at least int, so b + 1 is an int, and assigning an int to a byte is a narrowing conversion that needs an explicit cast.
A compound assignment E1 op= E2 is defined as E1 = (T) ((E1) op (E2)), where T is the type of E1. The cast is built in, so b += 1 compiles. b++ also includes the cast.
How I’d say it in the interview
"Because b plus 1 is promoted to int, and you can't assign int to byte without a cast. Compound assignment has an implicit cast back to the left-hand type, so plus-equals compiles."
Signals depth
Knowing that the implicit cast can silently overflow, and that byte c = 10 + 1; compiles because it's a constant expression that fits.
Red flags
"+= is just shorthand for b = b + 1." It isn't: the hidden cast is the whole point.
Follow-ups interviewers ask next
What if
bwas 127 beforeb += 1?It becomes
-128. Theintresult 128 is narrowed tobyteby keeping the low 8 bits,1000 0000, which is -128 in two's complement. No exception.What if I write
int i = 10; i += 3.7;?It compiles and
ibecomes13:(int) (10 + 3.7)truncates13.7. Plaini = i + 3.7wouldn't compile.
What does this print: long micros = 24 * 60 * 60 * 1000 * 1000;?
Tests int overflow, promotion order
Show answerHide answer
Model answer
long micros = 24 * 60 * 60 * 1000 * 1000;
System.out.println(micros); // 500654080
Every literal is an int, so the whole expression is evaluated in int. The true value, 86,400,000,000, overflows on the last multiplication and wraps; only the wrapped result is widened to long. The variable's type doesn't affect how the right-hand side is computed.
Fix it by making the first operand a long: 24L * 60 * 60 * 1000 * 1000 gives 86400000000. Even better for time, Duration.ofDays(1).toNanos() / 1000 or TimeUnit.DAYS.toMicros(1).
How I’d say it in the interview
"500654080, not 86.4 billion. The right side is all int arithmetic, it overflows silently, and only then gets widened. I'd make the first operand a long, or use Duration so units are explicit."
Signals depth
Explaining that promotion happens per operation, left to right, so putting L on the last literal only works by luck. Mentioning Math.multiplyExact to make overflow throw.
Red flags
"Java will automatically use long because the variable is long."
Follow-ups interviewers ask next
What if the interviewer asks for
Math.abs(Integer.MIN_VALUE)?It returns
-2147483648. There's no positive 2,147,483,648 in anint, so negation wraps back to the minimum.Math.absExact(Java 15+) throws instead. This is whyMath.abs(hash) % ncan still be negative; useMath.floorMod(hash, n).
What does this print, and why?
Tests autoboxing with overloads, Short vs Integer
Show answerHide answer
Model answer
Set<Short> set = new HashSet<>();
for (short i = 0; i < 100; i++) {
set.add(i);
set.remove(i - 1);
}
System.out.println(set.size()); // 100
It prints 100, not 1. i - 1 is an int expression (promotion), so it boxes to an Integer. remove(Object) then looks for an Integer in a set of Shorts, and Short.equals(Integer) is always false, so nothing is ever removed. It compiles because Set.remove takes Object, not E.
The fix is set.remove((short) (i - 1)).
How I’d say it in the interview
"100. i - 1 is promoted to int and boxes to Integer, and a Short never equals an Integer, so every remove misses. It compiles because remove takes Object."
Signals depth
Connecting two rules (numeric promotion and boxing to the promoted type), and noting that wrapper equals methods check the class first.
Red flags
Answering 1 without hesitation, or saying it throws ClassCastException.
Follow-ups interviewers ask next
What if it's a
List<Integer> list = new ArrayList<>(List.of(10, 20, 1));and I calllist.remove(1)?It removes the element at index 1 (20), leaving
[10, 1], becauseremove(int index)is an exact match and wins over boxing toremove(Object). To remove the value 1, calllist.remove(Integer.valueOf(1)).
Where do primitives live: on the stack or on the heap?
Tests frames vs objects, escape analysis
Show answerHide answer
Model answer
It depends on where the variable is declared, not on its type:
- Local variables and parameters (primitive or reference) live in the method's stack frame, in the local variable array.
longanddoubletake two slots. - Fields live inside their object, on the heap. A primitive
intfield is 4 bytes inside that object. - Array elements are always on the heap, because arrays are objects.
- Static fields are on the heap too in HotSpot, in the class's
Classmirror.
The JIT can change the picture: with escape analysis, an object that never escapes a method may be scalar-replaced, so its fields end up in registers and the object is never allocated.
How I’d say it in the interview
"Locals live in the stack frame, whatever their type; fields live inside their object on the heap. So an int field is on the heap. And escape analysis can scalar-replace objects that don't escape, so the JIT sometimes avoids the heap entirely."
Signals depth
Saying "scalar replacement" rather than "stack allocation", because HotSpot doesn't literally allocate objects on the stack.
Red flags
"Primitives are on the stack, objects are on the heap", full stop.
Follow-ups interviewers ask next
What if I ask how big a
booleanis?The JLS doesn't say. The JVM spec treats
booleanlocals asint, andboolean[]uses the byte-array instructions. On HotSpot that means 1 byte per field or array element and a 4-byte slot as a local. For dense flags, useBitSet.
What do these lines print: 'a' + 'b' + "c", "c" + 'a' + 'b' and (char) ('a' + 1)?
Tests char arithmetic, string concatenation order
Show answerHide answer
Model answer
System.out.println('a' + 'b' + "c"); // 195c
System.out.println("c" + 'a' + 'b'); // cab
System.out.println((char) ('a' + 1)); // b
System.out.println('a' + 1); // 98
+ is left-associative. In the first line, 'a' + 'b' is evaluated first: two chars promote to int, giving 97 + 98 = 195, and then "c" turns it into a string. In the second, the left operand is already a String, so every + is concatenation. A char is a 16-bit unsigned number, so 'a' + 1 is the int 98 until you cast it back.
How I’d say it in the interview
"195c, then cab. Plus goes left to right, and it only becomes string concatenation once a String has shown up. Before that, chars are just numbers promoted to int."
Signals depth
Knowing that a char is a UTF-16 code unit, not a character: an emoji is two chars, so "😀".length() is 2.
Red flags
Guessing abc for the first line.
Follow-ups interviewers ask next
What if I write
char c = 'a'; c = c + 1;?Compile error:
c + 1is anint.c += 1andc++compile (built-in cast), andchar d = 'a' + 1;compiles because it's a constant expression that fits in achar.
What do these casts print: (int) 3.99, (int) -3.99, (int) Double.NaN, (int) 1e20 and (byte) 200?
Tests narrowing conversions
Show answerHide answer
Model answer
(int) 3.99 // 3
(int) -3.99 // -3
(int) Double.NaN // 0
(int) 1e20 // 2147483647
(byte) 200 // -56
Floating point to integer truncates toward zero (not floor), maps NaN to 0 and clamps out-of-range values to Integer.MAX_VALUE or MIN_VALUE. Integer to smaller integer does something different: it keeps the low bits and discards the rest, with no clamping. 200 is 1100 1000; as a signed byte, that's -56.
How I’d say it in the interview
"3, -3, 0, Integer.MAX_VALUE and -56. Double to int truncates toward zero and saturates; int to byte just chops off the high bits, and the top remaining bit becomes the sign."
Signals depth
Knowing that (byte) 300.7 goes double → int (300) → byte (44), and that Math.round, Math.floor or Math.toIntExact are the tools when you mean rounding or want overflow to fail.
Red flags
Assuming casts round, or that (byte) 200 throws.
Follow-ups interviewers ask next
What if I cast
(float) 16_777_217?You get
1.6777216E7.int→floatis a widening conversion, yet it can lose precision, because afloathas only 24 significant bits. The same applies tolong→doubleabove 2⁵³.
A service crashes with NullPointerException on int count = counts.get(userId);. What's going on?
Tests unboxing null, helpful NPE messages
Show answerHide answer
Model answer
counts is a Map<String, Integer>. When the key is missing, get returns null, and assigning it to an int unboxes it by calling intValue() on null. Since Java 14, the message says so: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null.
Fixes: counts.getOrDefault(userId, 0), counts.merge(userId, 1, Integer::sum) for counters, or keep the value as Integer and handle null explicitly when "absent" means something.
How I’d say it in the interview
"It's an unboxing NPE. Map.get returned null for a missing key and assigning it to an int calls intValue on null. I'd use getOrDefault, or merge for counters."
Signals depth
Recognising intValue(), longValue() or booleanValue() in a stack trace as an unboxing failure, and mentioning the conditional-expression variant.
Red flags
"Wrap it in try/catch."
Follow-ups interviewers ask next
What if the line is
int x = flag ? cachedValue : 0;wherecachedValueis a nullInteger?Still an NPE. With an
Integerand anintoperand, the conditional expression's type isint, socachedValueis unboxed even though you never wrote an unboxing yourself.What if I print
true ? Integer.valueOf(1) : Double.valueOf(2)?1.0. Both operands are unboxed and binary numeric promotion makes the result adouble, so theInteger1 comes out as1.0.
Which line doesn't compile, and what does the rest print?
Tests default values, definite assignment
Show answerHide answer
Model answer
class Stats {
int total;
Integer boxed;
boolean ready;
void show() {
int local;
int[] arr = new int[2];
System.out.println(total + " " + boxed + " " + ready + " " + arr[0]);
System.out.println(local); // does not compile
}
}
System.out.println(local) fails: variable local might not have been initialized. Without it, the method prints 0 null false 0. Fields and array elements are zeroed by the JVM when the object or array is created. Local variables have no default; the compiler's definite assignment analysis (JLS chapter 16) rejects any read that isn't preceded by a write on every path.
How I’d say it in the interview
"The local read doesn't compile, because locals have no default and javac checks definite assignment. The rest prints 0, null, false, 0: fields and array elements get zero values. Note the wrapper field is null, not 0."
Signals depth
Pointing out that boxed is null, so boxed + 1 would throw, and that the array elements get defaults even though arr itself is a local.
Red flags
"Local ints default to 0."
Follow-ups interviewers ask next
What if
localis assigned inside anifwithout anelse?Still a compile error: it's not assigned on the path where the condition is false. Assigning in both branches, or using a
switchexpression, satisfies the compiler.
You change public static final int MAX_RETRIES = 3; to 5 in a shared library and redeploy the jar. A dependent service still retries 3 times. Why?
Tests compile-time constants, constant inlining, binary compatibility
Show answerHide answer
Model answer
MAX_RETRIES is a compile-time constant: final, primitive (or String) and initialised with a constant expression. javac doesn't compile Limits.MAX_RETRIES in the dependent service as a field read; it copies the value 3 into that service's bytecode (iconst_3). Swapping the library jar doesn't change the dependent's class files, so it keeps using 3 until it's recompiled.
Fixes: recompile dependents whenever a constant changes; or, for values that might change, don't make them compile-time constants. Expose a method (static int maxRetries()), read them from configuration, or initialise from a non-constant expression.
How I’d say it in the interview
"Because static final primitives and Strings with constant initialisers are inlined at compile time. The service has the literal 3 in its bytecode, so a new jar alone doesn't change it. Recompile the users, or expose values that can change through a method or config."
Signals depth
Knowing exactly what qualifies: static final Integer X = 3, static final int X = compute() and static final List<...> are not inlined. Bonus: reading a compile-time constant doesn't trigger the owning class's static initialiser.
Red flags
"The JVM caches static fields" or "the jar wasn't really deployed".
Follow-ups interviewers ask next
What if the constant were
public static final String VERSION = "1.0";?Same problem.
Stringconstants with literal initialisers are inlined just like primitives.
What's the type of list in var list = new ArrayList<>();, and is var dynamic typing?
Tests local variable type inference, diamond
Show answerHide answer
Model answer
ArrayList<Object>. The diamond normally infers its type argument from the target type on the left, but with var there is no target, so inference falls back to Object. You can now add a String and an Integer to the same list without a warning.
var (Java 10) is not dynamic typing. The compiler infers the variable's static type from the initialiser once, at compile time, and the bytecode is identical to writing the type yourself. var x = 1; x = "one"; is a compile error. It's only allowed for local variables with an initialiser (including for loops and try-with-resources): not for fields, parameters, return types, null, array initialisers or lambdas.
How I’d say it in the interview
"ArrayList of Object, because the diamond has nothing to infer from. And var isn't dynamic: javac infers a fixed static type from the initialiser, and the class file is the same as with an explicit type. I put the type argument on the right when I use var."
Signals depth
Explaining why var isn't allowed on fields and signatures (they're API, and inference would make an initialiser change an API change), and mentioning var lambda parameters (Java 11) for annotations.
Red flags
"It's like JavaScript's var" or "the type is decided at runtime".
Follow-ups interviewers ask next
What if I write
var f = () -> 42;?Compile error: a lambda has no type of its own and needs a target functional interface. Declare
Supplier<Integer> f = () -> 42;instead.What if I write
var var = 5;?It compiles.
varis a reserved type name, not a keyword, so it can still be a variable name. It just can't be the name of a class.