Skip to content

Functions — Interview Preparation

Junior Questions

# Question Expected Answer Focus
1 What is a function? Why do we use them? Named reusable block of code; DRY, abstraction, testability
2 What is the difference between a parameter and an argument? Parameter = variable in definition; argument = actual value at call site
3 What does "pass by value" vs "pass by reference" mean? Value = copy; reference = pointer to original data
4 What is scope? Explain local vs global scope. Region where a variable is accessible; local = inside function; global = everywhere
5 What happens if a function has no return statement? Go: zero values; Java (void): nothing; Python: returns None
6 What is a variadic function? Give examples. Accepts variable number of args; Go: ...int; Java: int...; Python: *args
7 What is the Python mutable default argument bug? def f(lst=[]) — list shared across calls; fix: use None
8 How does Go handle multiple return values? func f() (int, error) — natively returns multiple values

Middle Questions

# Question Expected Answer Focus
1 What is a closure? Give an example. Function that captures variables from enclosing scope; counter example
2 What are higher-order functions? Name three. Functions that take/return functions; map, filter, reduce
3 Explain the loop variable closure bug. All closures share same loop variable; fix: capture in local var or default param
4 When should you use recursion vs iteration? Recursion: trees, divide-conquer; iteration: simple loops, performance-critical
5 What is function composition? Combining functions: compose(f, g)(x) = f(g(x))
6 What is a callback function? Function passed as argument, called later; event handling, async patterns
7 What is the difference between map and flatMap? map transforms each element; flatMap transforms and flattens nested structures

Senior Questions

# Question Expected Answer Focus
1 How would you implement the middleware pattern? Chain of functions wrapping a handler; each adds behavior (logging, auth)
2 What is memoization? When would you NOT use it? Cache function results; don't use for impure functions, huge domains, or low-frequency calls
3 What is dependency injection through functions? Pass dependencies as function parameters instead of hardcoding; improves testability
4 How do you handle errors in a function pipeline? Result/Either types, Go error chaining, or try/catch with composition
5 Explain the difference between pure and impure functions. Pure: deterministic, no side effects; impure: I/O, mutation, non-determinism
6 How would you design a retry function with exponential backoff? Accept function + config, loop with increasing delay, return after success or max attempts

Professional Questions

# Question Expected Answer Focus
1 What is lambda calculus? What are its three constructs? Variable, abstraction (λx.M), application (M N); foundation of computation
2 What is the Y combinator? Why does it matter? Fixed-point combinator; enables recursion without self-reference in pure lambda calculus
3 What is tail call optimization? Which languages support it? Reuses stack frame for tail calls; Scheme yes, Go/Java/Python no
4 Explain the Curry-Howard correspondence. Types = propositions, programs = proofs, function type = implication
5 How do you prove a recursive function terminates? Find a well-founded measure (natural number) that strictly decreases each call
6 Use the Master Theorem to analyze merge sort. T(n) = 2T(n/2) + O(n); a=2, b=2, d=1; Case 2 → O(n log n)

Coding Challenge 1: Implement Memoize

Write a generic memoize function that caches results of any single-argument function.

Go

package main

import "fmt"

func memoize(fn func(int) int) func(int) int {
    cache := make(map[int]int)
    return func(n int) int {
        if val, ok := cache[n]; ok {
            return val
        }
        result := fn(n)
        cache[n] = result
        return result
    }
}

func main() {
    callCount := 0

    // Expensive function
    expensiveSquare := func(n int) int {
        callCount++
        fmt.Printf("  Computing square(%d)...\n", n)
        return n * n
    }

    memoSquare := memoize(expensiveSquare)

    fmt.Println("First calls:")
    fmt.Println(memoSquare(5))  // Computing square(5)... → 25
    fmt.Println(memoSquare(3))  // Computing square(3)... → 9
    fmt.Println(memoSquare(5))  // (cached) → 25
    fmt.Println(memoSquare(3))  // (cached) → 9

    fmt.Printf("Total computations: %d (should be 2)\n", callCount)
}

Java

import java.util.*;
import java.util.function.Function;

public class Memoize {
    public static <T, R> Function<T, R> memoize(Function<T, R> fn) {
        Map<T, R> cache = new HashMap<>();
        return input -> {
            if (cache.containsKey(input)) {
                return cache.get(input);
            }
            R result = fn.apply(input);
            cache.put(input, result);
            return result;
        };
    }

    public static void main(String[] args) {
        int[] callCount = {0};

        Function<Integer, Integer> expensiveSquare = n -> {
            callCount[0]++;
            System.out.println("  Computing square(" + n + ")...");
            return n * n;
        };

        Function<Integer, Integer> memoSquare = memoize(expensiveSquare);

        System.out.println("First calls:");
        System.out.println(memoSquare.apply(5));  // Computing... → 25
        System.out.println(memoSquare.apply(3));  // Computing... → 9
        System.out.println(memoSquare.apply(5));  // cached → 25
        System.out.println(memoSquare.apply(3));  // cached → 9

        System.out.println("Total computations: " + callCount[0] + " (should be 2)");
    }
}

Python

def memoize(fn):
    cache = {}
    def wrapper(*args):
        if args in cache:
            return cache[args]
        result = fn(*args)
        cache[args] = result
        return result
    wrapper.cache = cache
    return wrapper

# Test
call_count = 0

@memoize
def expensive_square(n):
    global call_count
    call_count += 1
    print(f"  Computing square({n})...")
    return n * n

print("First calls:")
print(expensive_square(5))  # Computing... → 25
print(expensive_square(3))  # Computing... → 9
print(expensive_square(5))  # cached → 25
print(expensive_square(3))  # cached → 9

print(f"Total computations: {call_count} (should be 2)")
print(f"Cache contents: {expensive_square.cache}")

Coding Challenge 2: Implement Retry with Exponential Backoff

Write a retry function that calls a given function up to N times, with exponential backoff between failures.

Go

package main

import (
    "errors"
    "fmt"
    "math/rand"
    "time"
)

type RetryConfig struct {
    MaxAttempts int
    BaseDelay   time.Duration
    MaxDelay    time.Duration
}

func retry(fn func() error, config RetryConfig) error {
    var lastErr error
    for attempt := 1; attempt <= config.MaxAttempts; attempt++ {
        err := fn()
        if err == nil {
            fmt.Printf("  Attempt %d: success\n", attempt)
            return nil
        }
        lastErr = err
        fmt.Printf("  Attempt %d: failed (%v)\n", attempt, err)

        if attempt < config.MaxAttempts {
            delay := config.BaseDelay * time.Duration(1<<uint(attempt-1))
            if delay > config.MaxDelay {
                delay = config.MaxDelay
            }
            // Add jitter: 50-100% of delay
            jitter := time.Duration(rand.Int63n(int64(delay / 2)))
            delay = delay/2 + jitter
            fmt.Printf("  Waiting %v before retry...\n", delay)
            time.Sleep(delay)
        }
    }
    return fmt.Errorf("all %d attempts failed: %w", config.MaxAttempts, lastErr)
}

func main() {
    callCount := 0
    err := retry(func() error {
        callCount++
        if callCount < 3 {
            return errors.New("service unavailable")
        }
        return nil // succeed on 3rd attempt
    }, RetryConfig{
        MaxAttempts: 5,
        BaseDelay:   100 * time.Millisecond,
        MaxDelay:    2 * time.Second,
    })

    if err != nil {
        fmt.Println("Final error:", err)
    } else {
        fmt.Println("Operation succeeded!")
    }
}

Java

import java.util.Random;
import java.util.concurrent.Callable;

public class Retry {

    public static <T> T retry(Callable<T> fn, int maxAttempts,
                               long baseDelayMs, long maxDelayMs) throws Exception {
        Exception lastException = null;
        Random rand = new Random();

        for (int attempt = 1; attempt <= maxAttempts; attempt++) {
            try {
                T result = fn.call();
                System.out.printf("  Attempt %d: success%n", attempt);
                return result;
            } catch (Exception e) {
                lastException = e;
                System.out.printf("  Attempt %d: failed (%s)%n", attempt, e.getMessage());

                if (attempt < maxAttempts) {
                    long delay = Math.min(baseDelayMs * (1L << (attempt - 1)), maxDelayMs);
                    long jitter = rand.nextLong(delay / 2);
                    delay = delay / 2 + jitter;
                    System.out.printf("  Waiting %dms before retry...%n", delay);
                    Thread.sleep(delay);
                }
            }
        }
        throw new RuntimeException("All " + maxAttempts + " attempts failed", lastException);
    }

    public static void main(String[] args) throws Exception {
        int[] callCount = {0};

        String result = retry(() -> {
            callCount[0]++;
            if (callCount[0] < 3) {
                throw new RuntimeException("service unavailable");
            }
            return "data loaded";
        }, 5, 100, 2000);

        System.out.println("Result: " + result);
    }
}

Python

import time
import random
import functools

def retry(fn, max_attempts=5, base_delay=0.1, max_delay=2.0):
    last_error = None
    for attempt in range(1, max_attempts + 1):
        try:
            result = fn()
            print(f"  Attempt {attempt}: success")
            return result
        except Exception as e:
            last_error = e
            print(f"  Attempt {attempt}: failed ({e})")

            if attempt < max_attempts:
                delay = min(base_delay * (2 ** (attempt - 1)), max_delay)
                jitter = random.uniform(delay * 0.5, delay)
                print(f"  Waiting {jitter:.3f}s before retry...")
                time.sleep(jitter)

    raise RuntimeError(f"All {max_attempts} attempts failed") from last_error


# Decorator version
def with_retry(max_attempts=5, base_delay=0.1, max_delay=2.0):
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            return retry(
                lambda: fn(*args, **kwargs),
                max_attempts, base_delay, max_delay
            )
        return wrapper
    return decorator


# Test
call_count = 0

@with_retry(max_attempts=5, base_delay=0.1)
def flaky_service():
    global call_count
    call_count += 1
    if call_count < 3:
        raise ConnectionError("service unavailable")
    return "data loaded"

result = flaky_service()
print(f"Result: {result}")

Coding Challenge 3: Implement Pipe/Compose

Implement both pipe (left-to-right) and compose (right-to-left) function combinators.

Go

package main

import (
    "fmt"
    "strings"
)

// Generic pipe for int→int functions
func pipe(fns ...func(int) int) func(int) int {
    return func(x int) int {
        result := x
        for _, fn := range fns {
            result = fn(result)
        }
        return result
    }
}

// Generic compose for int→int functions
func compose(fns ...func(int) int) func(int) int {
    return func(x int) int {
        result := x
        for i := len(fns) - 1; i >= 0; i-- {
            result = fns[i](result)
        }
        return result
    }
}

// String version for real-world use
func pipeStr(fns ...func(string) string) func(string) string {
    return func(s string) string {
        result := s
        for _, fn := range fns {
            result = fn(result)
        }
        return result
    }
}

func main() {
    double := func(x int) int { return x * 2 }
    addTen := func(x int) int { return x + 10 }
    square := func(x int) int { return x * x }

    // pipe: left-to-right → double(5)=10 → addTen(10)=20 → square(20)=400
    pipeline := pipe(double, addTen, square)
    fmt.Println(pipeline(5)) // 400

    // compose: right-to-left → square(5)=25 → addTen(25)=35 → double(35)=70
    composed := compose(double, addTen, square)
    fmt.Println(composed(5)) // 70

    // String pipeline: sanitize user input
    sanitize := pipeStr(
        strings.TrimSpace,
        strings.ToLower,
        func(s string) string { return strings.ReplaceAll(s, "  ", " ") },
    )
    fmt.Println(sanitize("  Hello   World  ")) // "hello world"
}

Java

import java.util.Arrays;
import java.util.function.Function;

public class PipeCompose {

    @SafeVarargs
    public static <T> Function<T, T> pipe(Function<T, T>... fns) {
        return Arrays.stream(fns)
            .reduce(Function.identity(), Function::andThen);
    }

    @SafeVarargs
    public static <T> Function<T, T> compose(Function<T, T>... fns) {
        return Arrays.stream(fns)
            .reduce(Function.identity(), Function::compose);
    }

    public static void main(String[] args) {
        Function<Integer, Integer> doubleIt = x -> x * 2;
        Function<Integer, Integer> addTen = x -> x + 10;
        Function<Integer, Integer> square = x -> x * x;

        // pipe: left-to-right
        Function<Integer, Integer> pipeline = pipe(doubleIt, addTen, square);
        System.out.println(pipeline.apply(5)); // 400

        // compose: right-to-left
        Function<Integer, Integer> composed = compose(doubleIt, addTen, square);
        System.out.println(composed.apply(5)); // 70

        // String pipeline
        Function<String, String> sanitize = pipe(
            String::trim,
            String::toLowerCase,
            s -> s.replaceAll("\\s+", " ")
        );
        System.out.println(sanitize.apply("  Hello   World  ")); // "hello world"
    }
}

Python

from functools import reduce

def pipe(*fns):
    """Left-to-right function composition: pipe(f, g)(x) = g(f(x))"""
    def piped(x):
        return reduce(lambda acc, fn: fn(acc), fns, x)
    return piped

def compose(*fns):
    """Right-to-left function composition: compose(f, g)(x) = f(g(x))"""
    def composed(x):
        return reduce(lambda acc, fn: fn(acc), reversed(fns), x)
    return composed

# Test
double = lambda x: x * 2
add_ten = lambda x: x + 10
square = lambda x: x ** 2

# pipe: left-to-right → double(5)=10 → add_ten(10)=20 → square(20)=400
pipeline = pipe(double, add_ten, square)
print(pipeline(5))  # 400

# compose: right-to-left → square(5)=25 → add_ten(25)=35 → double(35)=70
composed = compose(double, add_ten, square)
print(composed(5))  # 70

# String pipeline
sanitize = pipe(
    str.strip,
    str.lower,
    lambda s: " ".join(s.split()),  # collapse whitespace
)
print(sanitize("  Hello   World  "))  # "hello world"

# Typed pipe using type hints
from typing import TypeVar, Callable

T = TypeVar("T")

def typed_pipe(*fns: Callable[[T], T]) -> Callable[[T], T]:
    def piped(x: T) -> T:
        result = x
        for fn in fns:
            result = fn(result)
        return result
    return piped

Bonus: Quick-Fire Questions

# Question Answer
1 Can Go functions be generic? Yes, since Go 1.18 with type parameters
2 What is @functools.wraps in Python? Preserves original function's name, docstring, etc. when decorating
3 What is a method reference in Java? Shorthand for lambda: String::length instead of s -> s.length()
4 What does defer do in Go? Schedules a function call to run when the enclosing function returns
5 What is Python's recursion limit? Default 1000; changeable via sys.setrecursionlimit()
6 What is a @FunctionalInterface in Java? Interface with exactly one abstract method; enables lambda usage
7 What is the difference between *args and **kwargs? *args = positional tuple; **kwargs = keyword dict
8 What is a named return in Go? func f() (result int, err error) — named return values, can use bare return