Course contents

Data types & variables: interview questions & scenarios

Free12 questions · 18 what-if follow-ups

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.

Question 1. What does it print?Warm-up

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 answer3 follow-ups

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

  1. What if we compare c == 128 with a primitive instead?

    It's true. When one operand is a primitive int, the Integer is unboxed and the comparison is numeric.

  2. What if we start the JVM with -XX:AutoBoxCacheMax=1000?

    Then Integer.valueOf(128) comes from the cache too, and c == d prints true. Only the Integer upper bound is configurable; the lower bound stays -128, and Long, Short and Character caches are fixed. Code that depends on this is still a bug.

    1. What if it's Long x = 127L, y = 127L; and we call x.equals(127)?

      false. The argument 127 boxes to an Integer, and Long.equals returns false for anything that isn't a Long. x.equals(127L) is true.

Question 2. What does it print?Warm-up

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 answer2 follow-ups

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

  1. What if I ask what Double.NaN == Double.NaN prints?

    false. NaN is unequal to everything, including itself, under IEEE 754. Use Double.isNaN(x). Note that Double.valueOf(Double.NaN).equals(Double.NaN) is true, because equals compares bit patterns so NaN can work as a map key.

    1. What if I ask about 0.0 == -0.0?

      true with ==, but Double.compare(0.0, -0.0) is 1 and equals returns false. The two zeros are different bit patterns, and 1 / -0.0 is -Infinity.

Question 3. ConceptCore

Why does byte b = 10; b = b + 1; fail to compile, while b += 1; works?

Tests binary numeric promotion, compound assignment

Show answerHide answer2 follow-ups

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

  1. What if b was 127 before b += 1?

    It becomes -128. The int result 128 is narrowed to byte by keeping the low 8 bits, 1000 0000, which is -128 in two's complement. No exception.

    1. What if I write int i = 10; i += 3.7;?

      It compiles and i becomes 13: (int) (10 + 3.7) truncates 13.7. Plain i = i + 3.7 wouldn't compile.

Question 4. DebuggingCore

What does this print: long micros = 24 * 60 * 60 * 1000 * 1000;?

Tests int overflow, promotion order

Show answerHide answer1 follow-up

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

  1. What if the interviewer asks for Math.abs(Integer.MIN_VALUE)?

    It returns -2147483648. There's no positive 2,147,483,648 in an int, so negation wraps back to the minimum. Math.absExact (Java 15+) throws instead. This is why Math.abs(hash) % n can still be negative; use Math.floorMod(hash, n).

Question 5. What does it print?Senior

What does this print, and why?

Tests autoboxing with overloads, Short vs Integer

Show answerHide answer1 follow-up

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

  1. What if it's a List<Integer> list = new ArrayList<>(List.of(10, 20, 1)); and I call list.remove(1)?

    It removes the element at index 1 (20), leaving [10, 1], because remove(int index) is an exact match and wins over boxing to remove(Object). To remove the value 1, call list.remove(Integer.valueOf(1)).

Question 6. ConceptCore

Where do primitives live: on the stack or on the heap?

Tests frames vs objects, escape analysis

Show answerHide answer1 follow-up

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. long and double take two slots.
  • Fields live inside their object, on the heap. A primitive int field 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 Class mirror.

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

  1. What if I ask how big a boolean is?

    The JLS doesn't say. The JVM spec treats boolean locals as int, and boolean[] 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, use BitSet.

Question 7. What does it print?Warm-up

What do these lines print: 'a' + 'b' + "c", "c" + 'a' + 'b' and (char) ('a' + 1)?

Tests char arithmetic, string concatenation order

Show answerHide answer1 follow-up

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

  1. What if I write char c = 'a'; c = c + 1;?

    Compile error: c + 1 is an int. c += 1 and c++ compile (built-in cast), and char d = 'a' + 1; compiles because it's a constant expression that fits in a char.

Question 8. What does it print?Core

What do these casts print: (int) 3.99, (int) -3.99, (int) Double.NaN, (int) 1e20 and (byte) 200?

Tests narrowing conversions

Show answerHide answer1 follow-up

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

  1. What if I cast (float) 16_777_217?

    You get 1.6777216E7. int → float is a widening conversion, yet it can lose precision, because a float has only 24 significant bits. The same applies to long → double above 2⁵³.

Question 9. DebuggingCore

A service crashes with NullPointerException on int count = counts.get(userId);. What's going on?

Tests unboxing null, helpful NPE messages

Show answerHide answer2 follow-ups

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

  1. What if the line is int x = flag ? cachedValue : 0; where cachedValue is a null Integer?

    Still an NPE. With an Integer and an int operand, the conditional expression's type is int, so cachedValue is unboxed even though you never wrote an unboxing yourself.

    1. What if I print true ? Integer.valueOf(1) : Double.valueOf(2)?

      1.0. Both operands are unboxed and binary numeric promotion makes the result a double, so the Integer 1 comes out as 1.0.

Question 10. What does it print?Warm-up

Which line doesn't compile, and what does the rest print?

Tests default values, definite assignment

Show answerHide answer1 follow-up

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

  1. What if local is assigned inside an if without an else?

    Still a compile error: it's not assigned on the path where the condition is false. Assigning in both branches, or using a switch expression, satisfies the compiler.

Question 11. ScenarioSenior

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 answer1 follow-up

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

  1. What if the constant were public static final String VERSION = "1.0";?

    Same problem. String constants with literal initialisers are inlined just like primitives.

Question 12. ConceptCore

What's the type of list in var list = new ArrayList<>();, and is var dynamic typing?

Tests local variable type inference, diamond

Show answerHide answer2 follow-ups

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

  1. 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.

    1. What if I write var var = 5;?

      It compiles. var is 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.

Lessons in Data types & variables