#Toptal Screening Test Episode#01 in English #Freelance_Tips #Earn_Money #Freelancers #Developers
Interviews for Top Jobs at Toptal
Senior Software Engineer Interview
I applied online. The process took 2 days. I interviewed at Toptal in Apr 2022
I participated in a communication interview with a very simple and fluent English. I understood and answered all the questions asked. I was eliminated as I am not a native speaker, but it was an excellent experience.
- Communication specialist asked me experinces and tecnology know how.
Senior Software Engineer Interview
I interviewed at Toptal
Was given a take-home problem with intentional omissions. Had to build a simple web app, but requirements were incomplete. Turns out the omissions were the real test; they wanted me to push back and iron out the requirements more clearly–which I’d have done in a real world scenario, but for an interview task, seemed unnecessary.
- When reviewing my work, the interviewer clearly was running through a checklist and had little interest in discussing what I’d actually done.
Toptal sourced essential questions that the best Java developers and engineers can answer. Driven from our community, we encourage experts to submit questions and offer feedback.
Describe and compare fail-fast and fail-safe iterators. Give examples.
The main distinction between fail-fast and fail-safe iterators is whether or not the collection can be modified while it is being iterated. Fail-safe iterators allow this; fail-fast iterators do not.
ConcurrentModificationException. Some examples include
HashMap(most JDK1.4 collections are implemented to be fail-fast).
Vector are all implementations of the
List interface. Which of them is most efficient for adding and removing elements from the list? Explain your answer, including any other alternatives you may be aware of.
Of the three,
LinkedList is generally going to give you the best performance. Here’s why:
Vector each use an array to store the elements of the list. As a result, when an element is inserted into (or removed from) the middle of the list, the elements that follow must all be shifted accordingly.
Vector is synchronized, so if a thread-safe implementation is not needed, it is recommended to use
ArrayList rather than Vector.
LinkedList, on the other hand, is implemented using a doubly linked list. As a result, an inserting or removing an element only requires updating the links that immediately precede and follow the element being inserted or removed.
Why would it be more secure to store sensitive data (such as a password, social security number, etc.) in a character array rather than in a String?
In Java, Strings are immutable and are stored in the String pool. What this means is that, once a String is created, it stays in the pool in memory until being garbage collected. Therefore, even after you’re done processing the string value (e.g., the password), it remains available in memory for an indeterminate period of time thereafter (again, until being garbage collected) which you have no real control over. Therefore, anyone having access to a memory dump can potentially extract the sensitive data and exploit it.
In contrast, if you use a mutable object like a character array, for example, to store the value, you can set it to blank once you are done with it with confidence that it will no longer be retained in memory.
Apply to Join Toptals Development Network
and enjoy reliable, steady, remote Freelance Java Developer Jobs
What is the
ThreadLocal class? How and why would you use it?
ThreadLocal instance can store different values for each thread independently. Each thread that accesses the
set() method of a
ThreadLocal instance is accessing its own, independently initialized copy of the variable.
ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or transaction ID). The example below, from the
ThreadLocal Javadoc, generates unique identifiers local to each thread. A thread’s id is assigned the first time it invokes
ThreadId.get() and remains unchanged on subsequent calls.
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
What is the
volatile keyword? How and why would you use it?
In Java, each thread has its own stack, including its own copy of variables it can access. When the thread is created, it copies the value of all accessible variables into its own stack. The
volatile keyword basically says to the JVM “Warning, this variable may be modified in another Thread”.
In all versions of Java, the
volatile keyword guarantees global ordering on reads and writes to a variable. This implies that every thread accessing a volatile field will read the variable’s current value instead of (potentially) using a cached value.
In Java 5 or later,
volatile reads and writes establish a happens-before relationship, much like acquiring and releasing a mutex.
volatile may be faster than a lock, but it will not work in some situations. The range of situations in which volatile is effective was expanded in Java 5; in particular, double-checked locking now works correctly.
The volatile keyword is also useful for 64-bit types like long and double since they are written in two operations. Without the volatile keyword you risk stale or invalid values.
One common example for using
volatile is for a flag to terminate a thread. If you’ve started a thread, and you want to be able to safely interrupt it from a different thread, you can have the thread periodically check a flag (i.e., to stop it, set the flag to
true). By making the flag volatile, you can ensure that the thread that is checking its value will see that it has been set to
true without even having to use a synchronized block. For example:
wait() methods in Java, including when and why you would use one vs. the other.
sleep() is a blocking operation that keeps a hold on the monitor / lock of the shared object for the specified number of milliseconds.
wait(), on the other hand, simply pauses the thread until either (a) the specified number of milliseconds have elapsed or (b) it receives a desired notification from another thread (whichever is first), without keeping a hold on the monitor/lock of the shared object.
sleep() is most commonly used for polling, or to check for certain results, at a regular interval.
wait() is generally used in multithreaded applications, in conjunction with
notifyAll(), to achieve synchronization and avoid race conditions.
Tail recursion is functionally equivalent to iteration. Since Java does not yet support tail call optimization, describe how to transform a simple tail recursive function into a loop and why one is typically preferred over the other.
Here is an example of a typical recursive function, computing the arithmetic series 1, 2, 3…N. Notice how the addition is performed after the function call. For each recursive step, we add another frame to the stack.
Tail recursion occurs when the recursive call is in the tail position within its enclosing context – after the function calls itself, it performs no additional work. That is, once the base case is complete, the solution is apparent. For example:
Here you can see that
a plays the role of the accumulator – instead of computing the sum on the way down the stack, we compute it on the way up, effectively making the return trip unnecessary, since it stores no additional state and performs no further computation. Once we hit the base case, the work is done – below is that same function, “unrolled”.
Many functional languages natively support tail call optimization, however the JVM does not. In order to implement recursive functions in Java, we need to be aware of this limitation to avoid
StackOverflowErrors. In Java, iteration is almost universally preferred to recursion.
How can you swap the values of two numeric variables without using any other variables?
You can swap two values
b without using any other variables as follows:
How can you catch an exception thrown by another thread in Java?
This can be done using Thread.UncaughtExceptionHandler.
Here’s a simple example:
What is the Java Classloader? List and explain the purpose of the three types of class loaders.
The Java Classloader is the part of the Java runtime environment that loads classes on demand (lazy loading) into the JVM (Java Virtual Machine). Classes may be loaded from the local file system, a remote file system, or even the web.
When the JVM is started, three class loaders are used: 1. Bootstrap Classloader: Loads core java API file rt.jar from folder. 2. Extension Classloader: Loads jar files from folder. 3. System/Application Classloader: Loads jar files from path specified in the CLASSPATH environment variable.
finally block executed when an exception is thrown from a
try block that does not have a
catch block, and if so, when?
finally block is executed even if an exception is thrown or propagated to the calling code block.
Output can vary, being either:
When designing an abstract class, why should you avoid calling abstract methods inside its constructor?
This is a problem of initialization order. The subclass constructor will not have had a chance to run yet and there is no way to force it to run it before the parent class. Consider the following example class:
This seems like a good start for an abstract Widget: it allows subclasses to fill in
height, and caches their initial values. However, look when you spec out a typical subclass implementation like so:
Now we’ve introduced a subtle bug:
Widget.cachedHeight will always be zero for
SquareWidget instances! This is because the
this.size = size assignment occurs after the
Widget constructor runs.
Avoid calling abstract methods in your abstract classes’ constructors, as it restricts how those abstract methods can be implemented.
What variance is imposed on generic type parameters? How much control does Java give you over this?
Java’s generic type parameters are invariant. This means for any distinct types
G is not a subtype or supertype of
G. As a real world example,
List is not a supertype or subtype of
List. So even though
String extends (i.e. is a subtype of)
Object, both of the following assignments will fail to compile:
Java does give you some control over this in the form of use-site variance. On individual methods, we can use
? extends Type to create a covariant parameter. Here’s an example:
longs is a
List and not
List, it can be passed to
? super Type lets a method parameter be contravariant. Consider a function with a callback parameter:
Callback to be a subtype of
Callback , which means any callback that handles a supertype of
Number will do:
Note, however, that attempting to provide a callback that handles only
Long (a subtype of
Number) will rightly fail:
Liberal application of use-site variance can prevent many of the unsafe casts that often appear in Java code and is crucial when designing interfaces used by multiple developers.
What are static initializers and when would you use them?
A static initializer gives you the opportunity to run code during the initial loading of a class and it guarantees that this code will only run once and will finish running before your class can be accessed in any way.
They are useful for performing initialization of complex static objects or to register a type with a static registry, as JDBC drivers do.
Suppose you want to create a static, immutable
Map containing some feature flags. Java doesn’t have a good one-liner for initializing maps, so you can use static initializers instead:
Within the same class, you can repeat this pattern of declaring a static field and immediately initializing it, since multiple static initializers are allowed.
If one needs a
Set, how do you choose between
At first glance,
HashSet is superior in almost every way: O(1)
contains, vs. O(log(N)) for
TreeSet is indispensable when you wish to maintain order over the inserted elements or query for a range of elements within the set.
Set of timestamped
Event objects. They could be stored in a
hashCode based on that timestamp. This is efficient storage and permits looking up events by a specific timestamp, but how would you get all events that happened on any given day? That would require a O(n) traversal of the
HashSet, but it’s only a O(log(n)) operation with
TreeSet using the
Event happens to be a class that we cannot extend or that doesn’t implement
TreeSet allows us to pass in our own
TreeSet is a good choice when order matters and when reads are balanced against the increased cost of writes.
What are method references, and how are they useful?
Method references were introduced in Java 8 and allow constructors and methods (static or otherwise) to be used as lambdas. They allow one to discard the boilerplate of a lambda when the method reference matches an expected signature.
For example, suppose we have a service that must be stopped by a shutdown hook. Before Java 8, we would have code like this:
With lambdas, this can be cut down considerably:
stop matches the signature of
void return type, no parameters), and so we can introduce a method reference to the
stop method of that specific
This is terse (as opposed to verbose code) and clearly communicates what is going on.
Method references don’t need to be tied to a specific instance, either; one can also use a method reference to an arbitrary object, which is useful in
Stream operations. For example, suppose we have a
Person class and want just the lowercase names of a collection of people:
A complex lambda can also be pushed into a static or instance method and then used via a method reference instead. This makes the code more reusable and testable than if it were “trapped” in the lambda.
So we can see that method references are mainly used to improve code organization, clarity and terseness.
How are Java enums more powerful than integer constants? How can this capability be used?
Enums are essentially final classes with a fixed number of instances. They can implement interfaces but cannot extend another class.
This flexibility is useful in implementing the strategy pattern, for example, when the number of strategies is fixed. Consider an address book that records multiple methods of contact. We can represent these methods as an enum and attach fields, like the filename of the icon to display in the UI, and any corresponding behaviour, like how to initiate contact via that method:
We can dispense with
switch statements entirely by simply using instances of
This is just the beginning of what can be done with enums. Generally, the safety and flexibility of enums means they should be used in place of integer constants, and switch statements can be eliminated with liberal use of abstract methods.
What does it mean for a collection to be “backed by” another? Give an example of when this property is useful.
If a collection backs another, it means that changes in one are reflected in the other and vice-versa.
For example, suppose we wanted to create a
whitelist function that removes invalid keys from a
Map. This is made far easier with
Map.keySet, which returns a set of keys that is backed by the original map. When we remove keys from the key set, they are also removed from the backing map:
retainAll writes through to the backing map, and allows us to easily implement something that would otherwise require iterating over the entries in the input map, comparing them against
Note, it is important to consult the documentation of the backing collection to see which modifications will successfully write through. In the example above,
map.keySet().add(value) would fail, because we cannot add a key to the backing map without a value.
What is reflection? Give an example of functionality that can only be implemented using reflection.
Reflection allows programmatic access to information about a Java program’s types. Commonly used information includes: methods and fields available on a class, interfaces implemented by a class, and the runtime-retained annotations on classes, fields and methods.
Examples given are likely to include:
Requestobject, returns a
Response), and finally, invoke the method.
@Injectto discover if they require injection of a bean, and also to set those values.
A concrete code example could be something simple, like copying an object’s fields into a map:
Such tricks can be useful for debugging, or for utility methods such as a
toString method that works on any class.
Aside from implementing generic libraries, direct use of reflection is rare but it is still a handy tool to have. Knowledge of reflection is also useful for when these mechanisms fail.
However, it is often prudent to avoid reflection unless it is strictly necessary, as it can turn straightforward compiler errors into runtime errors.
Nested classes can be static or non-static (also called an inner class). How do you decide which to use? Does it matter?
The key difference between is that inner classes have full access to the fields and methods of the enclosing class. This can be convenient for event handlers, but comes at a cost: every instance of an inner class retains and requires a reference to its enclosing class.
With this cost in mind, there are many situations where we should prefer static nested classes. When instances of the nested class will outlive instances of the enclosing class, the nested class should be static to prevent memory leaks. Consider this implementation of the factory pattern:
At a glance, this design looks good: the
WidgetParserFactory hides the implementation details of the parser with the nested class
WidgetParserImpl is not static, and so if
WidgetParserFactory is discarded immediately after the
WidgetParser is created, the factory will leak, along with all the references it holds.
WidgetParserImpl should be made static, and if it needs access to any of
WidgetParserFactory’s internals, they should be passed into
WidgetParserImpl’s constructor instead. This also makes it easier to extract
WidgetParserImpl into a separate class should it outgrow its enclosing class.
Inner classes are also harder to construct via reflection due to their “hidden” reference to the enclosing class, and this reference can get sucked in during reflection-based serialization, which is probably not intended.
So we can see that the decision of whether to make a nested class static is important, and that one should aim to make nested classes static in cases where instances will “escape” the enclosing class or if reflection on those nested classes is involved.
What is the difference between
String s = "Test" and
String s = new String("Test")? Which is better and why?
String s = "Test" is more efficient to use than
String s = new String("Test").
In the case of
String s = "Test", a String with the value “Test” will be created in the String pool. If another String with the same value is then created (e.g.,
String s2 = "Test"), it will reference this same object in the String pool.
However, if you use
String s = new String("Test"), in addition to creating a String with the value “Test” in the String pool, that String object will then be passed to the constructor of the String Object (i.e.,
new String("Test")) and will create another String object (not in the String pool) with that value. Each such call will therefore create an additional String object (e.g.,
String s2 = new String("Test") would create an addition String object, rather than just reusing the same String object from the String pool).
There is more to interviewing than tricky technical questions, so these are intended merely as a guide. Not every “A” candidate worth hiring will be able to answer them all, nor does answering them all guarantee an “A” candidate. At the end of the day, hiring remains an art, a science — and a lot of work.
Tired of interviewing candidates? Not sure what to ask to get you a top hire?
Let Toptal find the best people for you.
Our Exclusive Network of Java Developers
Looking to land a job as a Java Developer?
Let Toptal find the right job for you.
Job Opportunities From Our Network
Submitted questions and answers are subject to review and editing, and may or may not be selected for posting, at the sole discretion of Toptal, LLC.
Radek is a certified Toptal blockchain engineer particularly interested in Ethereum and smart contracts. In the fiat world, he is experienced in big data and machine learning projects. He is a triple winner in two different international IBM Apache Spark competitions, co-creator of PlayStation 4s back end, a successful hackathon competitor, and speaker at conferences in Australia, Poland, and Serbia.
Rizwan has a reputation for overcoming complex challenges through clear thinking, innovative approaches, and enhancing the communication between different parts of organizations. Throughout his career, he has optimized the efforts of diverse and dispersed teams of IT professionals and consistently has delivered projects profitably in challenging environments.
James has had a long career providing technology solutions that solve challenging problems. Software engineering is his craft, and he always seeks to learn to improve the quality of his work continuously. His past employers and co-workers value James for his ability to quickly understand a problem domain, for his clear and open communication, and for producing timely, well-organized technological solutions.
Looking for Java Developers? Check out Toptal’s Java developers.
Toptal Connects the Top 3% of Freelance Talent All Over The World.
Users who have contributed to this file
Please read the contribution guidelines or creating a list guide if you want to contribute.
Advantages of a Stream-based Approach in Java
Streams are more expressive. With streams, the logic lies bundled together in 3-4 lines, unlike a
for loop where we need to go deep into the loop to understand the logic.
Streams can be parallelized to whatever extent multiple CPU cores are available. The thread life cycle methods to create parallel threads are abstracted from the developer, while in a
for loop, if we want to achieve parallelism, then we need to implement our own thread life cycle methods.
Streams support lazy loading. This means that the intermediate operations just create other streams, and won’t be processed until the terminal operation is called.
Now, what if the requirement was to find any player whose batting average is at least 45? Then the code would have been:
The above code looks like it will find all players whose average is greater than 45, and then will return any player out of it. But this statement is not true. What actually happens behind the scenes is, when the terminal operation
findAny() is called, the
filter stream is processed, and as soon as a
Player is found with an average of at least 45, that
Player is returned without processing any other elements.
Short-circuiting. This is used to terminate the processing once a condition is met, so in the above example
findAny() acts as short-circuit and terminates the processing as soon as the first
Player is found meeting the requirement. This is somewhat synonymous to
break in a loop.
How do I prepare for a Toptal interview?
- step1. Language and Personality. The first step of the screening process is a comprehensive English language and communication interview evaluation. …
- step2. In-Depth Skill Review. …
- step3. Live Screening. …
- step4. Test Projects. …
- step5. Continued Excellence.
What are Toptal interview questions?
How do I introduce myself in a Java developer interview?
A sample answer could be: “For the past four years, I’ve been working on backend development. I take an evaluative approach to solving issues and work through the process by trying out various solutions. I’m a self-starter and work well independently, but I’m also skilled at working in teams.