Menu
CoddyTech

Java 8 Interview Questions

Lambdas, functional interfaces, the Stream API and Optional, including how streams evaluate.

10 questions
  1. What are the main features introduced in Java 8?

    Lambdas, functional interfaces (Function, Predicate, Supplier, Consumer), the Stream API, method references, default and static interface methods, Optional, and java.time (LocalDate, Instant) replacing Date and Calendar. Metaspace also replaced PermGen.

  2. What is a functional interface?

    An interface with exactly one abstract method, so a lambda or method reference can implement it; default and static methods do not count. @FunctionalInterface makes the compiler enforce that.

    Java
    import java.util.function.Function;
    import java.util.function.Predicate;
    import java.util.function.Supplier;
    
    @FunctionalInterface
    interface Discount {
        double apply(double price);
    }
    
    public class Main {
        public static void main(String[] args) {
            Discount tenOff = p -> p * 0.9;
            Predicate<String> isEmpty = String::isEmpty;
            Function<String, Integer> len = String::length;
            Supplier<String> hello = () -> "hello";
    
            System.out.println(tenOff.apply(200));
            System.out.println(isEmpty.test("") + " " + len.apply("java") + " " + hello.get());
        }
    }

    It prints 180.0 and true 4 hello. See lambda expressions.

  3. How do Java streams work? Show filter, map and reduce.

    A stream is a source, lazy intermediate operations (filter, map, sorted) and one terminal operation (collect, reduce, count) that runs the pipeline.

    Java
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class Main {
        public static void main(String[] args) {
            List<Integer> nums = List.of(5, 3, 8, 1, 9, 2);
    
            List<Integer> evensSquared = nums.stream()
                    .filter(n -> n % 2 == 0)
                    .map(n -> n * n)
                    .toList();
            int sum = nums.stream().reduce(0, Integer::sum);
            String joined = nums.stream().sorted().map(String::valueOf)
                    .collect(Collectors.joining("-"));
    
            System.out.println(evensSquared + " " + sum + " " + joined);
        }
    }

    It prints [64, 4] 28 1-2-3-5-8-9. A stream is single use: a second terminal operation throws IllegalStateException. More in the streams guide.

  4. Does a stream filter run without a terminal operation?

    Java
    import java.util.stream.Stream;
    
    public class Main {
        public static void main(String[] args) {
            Stream<Integer> s = Stream.of(1, 2, 3).filter(x -> {
                System.out.print("f" + x + " ");
                return x > 1;
            });
            System.out.println("done");
        }
    }

    It prints only done. Intermediate operations are lazy: with no terminal operation no element flows, so the filter lambda never runs. The same laziness lets findFirst() stop at the first match.

  5. In what order do map and forEach run on a stream?

    Java
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Stream;
    
    public class Main {
        public static void main(String[] args) {
            List<String> log = new ArrayList<>();
            Stream.of("a", "b")
                  .map(s -> { log.add("map " + s); return s; })
                  .forEach(s -> log.add("each " + s));
            System.out.println(String.join(", ", log));
        }
    }

    It prints map a, each a, map b, each b. Each element goes through the whole pipeline before the next one enters.

    A stateful step changes that: with sorted() before forEach, every element is mapped before any reaches forEach.

  6. What is the difference between map and flatMap in streams?

    map gives one output per element; flatMap maps each element to a stream and flattens the results into one stream.

    Java
    import java.util.Arrays;
    import java.util.List;
    
    public class Main {
        public static void main(String[] args) {
            List<String> lines = List.of("to be", "or not");
    
            List<Integer> lengths = lines.stream().map(String::length).toList();
            List<String> words = lines.stream()
                    .flatMap(line -> Arrays.stream(line.split(" ")))
                    .toList();
    
            System.out.println(lengths);
            System.out.println(words);
        }
    }

    It prints [5, 6] and [to, be, or, not].

  7. How do you group elements with streams? Explain Collectors.groupingBy.

    groupingBy(classifier) collects into a Map from each key to the list of elements with that key; a downstream collector like counting() changes the values.

    Java
    import java.util.List;
    import java.util.Map;
    import java.util.TreeMap;
    import java.util.stream.Collectors;
    
    public class Main {
        public static void main(String[] args) {
            List<String> words = List.of("java", "spring", "jvm", "stream", "lambda", "jdk");
    
            Map<Integer, List<String>> byLength = words.stream()
                    .collect(Collectors.groupingBy(String::length, TreeMap::new, Collectors.toList()));
            Map<Character, Long> countByFirst = words.stream()
                    .collect(Collectors.groupingBy(w -> w.charAt(0), TreeMap::new, Collectors.counting()));
    
            System.out.println(byLength);
            System.out.println(countByFirst);
        }
    }

    It prints {3=[jvm, jdk], 4=[java], 6=[spring, stream, lambda]} and {j=3, l=1, s=2}. The one-argument form promises no map type or order, hence TreeMap::new.

    Practice it: Group Anagrams

  8. What is Optional? What is the difference between orElse and orElseGet?

    Optional<T> is a return type for a value that may be absent. orElse(x) always evaluates x; orElseGet(supplier) runs the supplier only when the Optional is empty.

    Java
    import java.util.Optional;
    
    public class Main {
        static String loadDefault() {
            System.out.println("  loadDefault() called");
            return "guest";
        }
    
        public static void main(String[] args) {
            Optional<String> user = Optional.of("asha");
    
            System.out.println("orElse:");
            String a = user.orElse(loadDefault());
            System.out.println("orElseGet:");
            String b = user.orElseGet(Main::loadDefault);
    
            System.out.println(a + " " + b);
            System.out.println(Optional.<String>empty().map(String::toUpperCase).orElse("none"));
        }
    }

    loadDefault() called appears only after orElse:, and the last two lines are asha asha and none. Do not use Optional for fields or parameters. See Optional.

  9. What happens when Collectors.toMap meets a duplicate key?

    It throws IllegalStateException. Pass a merge function as the third argument to decide what the key keeps, and a map supplier as the fourth to choose the map type.

    Java
    import java.util.List;
    import java.util.Map;
    import java.util.TreeMap;
    import java.util.stream.Collectors;
    
    public class Main {
        public static void main(String[] args) {
            List<String> words = List.of("apple", "avocado", "banana");
    
            try {
                words.stream().collect(Collectors.toMap(w -> w.charAt(0), w -> w));
            } catch (IllegalStateException e) {
                System.out.println(e.getMessage());
            }
    
            Map<Character, String> merged = words.stream()
                    .collect(Collectors.toMap(w -> w.charAt(0), w -> w,
                            (a, b) -> a + "+" + b,   // merge function for duplicates
                            TreeMap::new));
            System.out.println(merged);
        }
    }

    It prints Duplicate key a (attempted merging values apple and avocado) and {a=apple+avocado, b=banana}. toMap also throws NullPointerException when a value is null.

  10. When should you use a parallel stream, and when should you avoid it?

    Use one for CPU-heavy work on large data that splits easily (arrays, ArrayList, IntStream.range) with no shared mutable state. Avoid it for small inputs, blocking I/O, LinkedList or Stream.iterate sources, and lambdas that write to shared collections.

    Java
    import java.util.List;
    import java.util.stream.IntStream;
    
    public class Main {
        public static void main(String[] args) {
            long sum = IntStream.rangeClosed(1, 1_000_000).parallel().asLongStream().sum();
            List<Integer> doubled = List.of(1, 2, 3, 4).parallelStream().map(n -> n * 2).toList();
    
            System.out.println(sum);
            System.out.println(doubled);   // toList keeps encounter order
        }
    }

    It prints 500000500000 and [2, 4, 6, 8]: toList() keeps encounter order, while forEach on a parallel stream does not. All parallel streams share the common ForkJoinPool, so one slow task slows the others. Measure before switching.

Coddy programming languages illustration

Learn to code with Coddy

Get started