Java Algorithms Interview Challenger
Java Algorithms Interview Challenger
https://t.me/javalib
Contents
Memory Allocation . . . . . . . . . . . . . . . . . . . . . . . 13
Data in Binary Number . . . . . . . . . . . . . . . . . . . . 13
Contiguous Memory Slots Allocation . . . . . . . . . . . . 14
Array Allocation . . . . . . . . . . . . . . . . . . . . . . . 15
Static Memory Allocation . . . . . . . . . . . . . . . . . . 17
Dynamic Memory Allocation . . . . . . . . . . . . . . . . 17
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Big O Notation . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Asymptotic Notations . . . . . . . . . . . . . . . . . . . . . 19
Big O Notation in Practice . . . . . . . . . . . . . . . . . . 20
Constant – O(1) . . . . . . . . . . . . . . . . . . . . . . . . 20
Accessing an Array by Index . . . . . . . . . . . . . . . . 22
Logarithmic – O(log n) . . . . . . . . . . . . . . . . . . . . 22
Linear – O(n) . . . . . . . . . . . . . . . . . . . . . . . . . 25
https://t.me/javalib
CONTENTS
Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Array Memory Allocation . . . . . . . . . . . . . . . . . . 35
Static Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Insert an Element in the Middle of the Array . . . . . . . 37
Dynamic Arrays . . . . . . . . . . . . . . . . . . . . . . . . 38
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Get a character from a String . . . . . . . . . . . . . . . . 43
Copy a String . . . . . . . . . . . . . . . . . . . . . . . . . 44
String Encodings What to Use? . . . . . . . . . . . . . . . 47
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Hashtable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
What is a Hash Function? . . . . . . . . . . . . . . . . . . 51
Hash function Collision . . . . . . . . . . . . . . . . . . . 52
Hash collision in practice with Java . . . . . . . . . . . . . 54
Optimizing Hash Collision in Java . . . . . . . . . . . . . 57
Optimizing Hash Collision in Java . . . . . . . . . . . . . 60
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Singly Linked List Structure . . . . . . . . . . . . . . . . . 64
Add Elements to Linked List . . . . . . . . . . . . . . . . . 66
Search Element from Linked List . . . . . . . . . . . . . . 68
Inserting Element in the middle of a Linked List . . . . . 70
Doubly Linked List . . . . . . . . . . . . . . . . . . . . . . 71
https://t.me/javalib
CONTENTS
Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Inserting and Removing Elements from a Stack with Java 77
Stack inherits Vector . . . . . . . . . . . . . . . . . . . . . 79
Using Stack with Deque and ArrayDeque . . . . . . . . . 80
Time Complexity from Stack . . . . . . . . . . . . . . . . 82
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
What is Queue? . . . . . . . . . . . . . . . . . . . . . . . . 85
Inserting Elements at the Start and the End of the Queue 88
Deleting Elements at the Start and End of the Queue . . . 89
Big(O) Notation – Time Complexity of a Queue . . . . . 90
Delete the first element of the queue . . . . . . . . . . . . 92
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Undirected Graph . . . . . . . . . . . . . . . . . . . . . . . 98
Directed Graph . . . . . . . . . . . . . . . . . . . . . . . . 99
Acyclic and Cyclic Graph . . . . . . . . . . . . . . . . . . 101
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Binary Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Ternary Tree . . . . . . . . . . . . . . . . . . . . . . . . . . 109
K-ary tree . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Perfect Binary Tree . . . . . . . . . . . . . . . . . . . . . . 111
Complete Binary Tree . . . . . . . . . . . . . . . . . . . . . 111
Full Binary Tree . . . . . . . . . . . . . . . . . . . . . . . . 112
Balanced Binary Tree . . . . . . . . . . . . . . . . . . . . . 113
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
https://t.me/javalib
CONTENTS
Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
Seeing LIFO/FILO in practice . . . . . . . . . . . . . . . . 120
Recursion Tree with Fibonacci . . . . . . . . . . . . . . . . 122
Big(O) Notation for Recursive Fibonacci . . . . . . . . . . 124
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Logarithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Logarithm Time Complexity in Binary Search . . . . . . 127
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Choosing the pivot with the Quicksort Algorithm . . . . 148
Creating Partitions with the Quicksort Algorithm . . . . 148
Creating Partition with Left and Right Pointers . . . . . . 151
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
https://t.me/javalib
Depth-first-search . . . . . . . . . . . . . . . . . . . . . . . . 162
Preorder Traversal . . . . . . . . . . . . . . . . . . . . . . . 165
Recursive Preorder Traversal without Lambda . . . . . . 165
Recursive Preorder Traversal with Lambda . . . . . . . . 166
Preorder with Looping . . . . . . . . . . . . . . . . . . . . 167
Postorder Traversal . . . . . . . . . . . . . . . . . . . . . . 168
Recursive Postorder traversal without Lambdas . . . . . . 168
Recursive Postorder traversal with Lambda . . . . . . . . 169
Postorder traversal with looping . . . . . . . . . . . . . . 169
Inorder Traversal . . . . . . . . . . . . . . . . . . . . . . . 171
Recursive In-order Traversal . . . . . . . . . . . . . . . . . 171
In-order Traversal with Looping . . . . . . . . . . . . . . . 173
Big (O) Notation for Depth First Search . . . . . . . . . . 174
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
https://t.me/javalib
Java Interview Process
Interviews are difficult and many times tricky, and if we don’t
know what the rules are to pass in an interview, we are very likely
to fail.
One important point to remember when doing interviews is that
they don’t necessarily test your skills as a software engineer. The
interviews have their gaps, and they could be better. For example,
if it’s been a while since we don’t practice algorithms and need to
know some data structures, we will likely fail the interview.
Therefore, understanding the rules of the game is very important.
Otherwise, we won’t pass on those interviews.
Being rejected in many interviews and feeling frustrated is a normal
feeling. I’ve been rejected in many interviews and felt frustrated,
but we need to remember all the time that interviews will not
define how good we are as software engineers. It will test some
skills, but the day-to-day work is completely different.
That’s why I created this chapter so you can understand the rules
of the interview game so you can pass on those interviews.
Interview Mindset
Don’t be intimidated by the amount of technologies you see in a job
spec because no one really knows all of those technologies in depth.
Instead, knowing what those technologies do is usually enough.
Also, see failing in an interview as a necessary step for your growth.
Failure is just a stepping stone to bring you success. The crucial
point is to learn why we failed, improve and try again.
https://t.me/javalib
Java Interview Process 2
CV
Your CV has to have relevant information regarding what the
market is asking for. Suppose you put in your CV that you know
Struts or any other obsolete technology. That won’t matter. That’s
why you must align your CV with the market’s requirements.
That doesn’t mean that you must lie on your CV. You need instead
to be honest because they will ask you about the concepts you
included in your CV during the interview. They might disqualify
you immediately if you don’t know the technology because the
interviewer will notice that.
Make sure to put the biggest highlights you had in your job
experience. On the top of your CV, include how many years
of experience you have and the results you got for previous
companies. Try to use numbers. For example, if you refactored
lots of code from a previous company, you can state on your CV
the following.
If you think about your previous experiences, you will have plenty
of ideas about the results you brought.
https://t.me/javalib
Java Interview Process 3
Market Yourself
Your CV is a way to market yourself, but an even stronger way to
market yourself is to share your knowledge. You build trust with
people who never met you by sharing your knowledge.
You can also combine your knowledge sharing with your CV. You
can include links from your blog, Youtube channel, or talks you
gave.
Also, you can skip interviews entirely if you are well known to solve
a specific problem that companies need very much. If a person
needs the problem you solve to be solved, they will ask you to join
their company, and they will ask you how much you want to solve
this problem.
Having a blog
Having a blog is a great investment in yourself and your career.
That’s because you will be improving your communications skills
and will be filling out the knowledge gaps you have. The ultimate
way to learn anything is by sharing what you know.
If you decide to create a blog, make sure you are consistent. You
will also need to determine a focus. Meaning what problem you
will solve that will make you stand out for companies. Let’s see
some problems:
• Performance
• Bugs
• Security
• Data
• Cloud Resilience
You can also share your knowledge on something you are learning.
Let’s suppose you are learning AWS concepts; you can share this
https://t.me/javalib
Java Interview Process 4
knowledge since that will be the most powerful way for you to
really master this content.
Decide how often you will post a new article. You can post an
article per week, bi-weekly, or monthly. Once you decide on it, it’s
crucial to stick to it. To stick to it, create a list of 20 article titles,
and you will have articles for almost half a year if you decide to
develop weekly articles.
You can get started creating articles on:
• https://dev.to
• https://medium.com
• https://www.linkedin.com/1
• https://www.bluehost.com
Youtube Channel
If you prefer videos, create your Youtube channel and share what
you know. Having a focus on a problem that companies face is
also powerful because then the people who see your video and
understand you can help then you will probably be hired without
going through several interview steps.
Giving Talks
You have the option of also giving talks. Giving talks is a great way
for you to market yourself and get visibility. When you give a talk,
1 https://www.linkedin.com
https://t.me/javalib
Java Interview Process 5
even if you are not an expert on the subject, you will be perceived
as an expert.
If you want to start giving talks, start small. Give a talk to a friend
of yours, give a talk online, and then expand gradually.
Also, get involved in Java user groups from your city and help
them in some way. Gain influence and trust, and once you have
something to present, ask them to do a lighting talk of 5 or 15
minutes. That will be a powerful way for you to build up your
confidence, share your knowledge, and gain exposure.
• https://github.com/eclipse/jnosql
https://t.me/javalib
Java Interview Process 6
• https://github.com/quarkusio/quarkus
• https://github.com/elastic/elasticsearch
• https://github.com/square/okhttp
• https://github.com/google/guava
• https://github.com/spring-projects/spring-boot
• https://github.com/jenkinsci/jenkins
• https://github.com/google/guice
• https://github.com/mockito/mockito
Create your pull request and get your software engineering skills
to a whole new level!
Interview Modalities
I’ve done many interviews throughout my career, and they were
very different from each other. When I was starting my career,
knowing Object-Oriented programming and basic sort algorithms
was enough
Obviously, as my career progressed, the interviews for higher levels
were more difficult. The interview for an intermediate developer
some time ago was enough to know Java EE, now called Jakarta
EE.
https://t.me/javalib
Java Interview Process 7
https://t.me/javalib
Java Interview Process 8
Code Quality
It’s crucial to know how to create good quality code, not reinvent
the wheel with technologies, and understand what you do with
software development.
One of the most important principles is to create cohesive code
(code that does ONE thing very well) and code with low coupling
(code that doesn’t have strong dependency).
Another crucial point is correctly naming projects, packages,
classes, methods, and variables. Avoid acronyms and use the name
conventions for the Java language.
Mastering paradigms like Object-Oriented Programming (OOP)
and Functional Programming will help develop good-quality code.
SOLID principles are powerful in guiding you to create high-quality
code. Therefore, it’s crucial to learn those concepts well.
The last component to help you create high-quality code is to
master the most essential design patterns. We can’t overuse design
patterns. We should use them only in suitable situations instead.
Otherwise, the code will only get more complicated.
Code Design
We need to have a basic understanding of code design to develop
systems and also to pass on interviews. :)
Sometimes, there will be interviews that will ask you how you
design your code and how many layers you use.
In Java, it’s common to have the controller layer (the one that has
the API endpoints), then the service that has business requirements
implementations, the repository that interacts with the database,
the data objects, and the database entities objects.
https://t.me/javalib
Java Interview Process 9
Algorithms Interview
This interview is nowadays very popular. A lot of companies
(small, large companies) are following the same process for inter-
views.
https://t.me/javalib
Java Interview Process 10
https://t.me/javalib
Java Interview Process 11
have to read and understand their problem and then design the
system explaining why of your choices.
In the Systems Design interview, it’s crucial that you have some
knowledge of how to create a system.
Summary
Interviews are annoying, but it’s necessary to know what are the
rules of those interviews. Otherwise, we will fail no matter how
good of software engineers we are.
A good motivation, though, is to think that by getting good with
data structures, algorithms, and Systems Design, we will become
better software engineers.
Therefore, let’s see what were the main points of this article:
https://t.me/javalib
Java Interview Process 12
https://t.me/javalib
Memory Allocation
Every time we create a variable, invoke a method, or create an
instance memory allocation will happen in Java and any other
programming language. Data is stored in the form of bits, each
memory slot can hold 1 byte which is the same as 8 bits.
Let’s see the chart of how many bits each variable from Java uses:
https://t.me/javalib
Memory Allocation 14
https://t.me/javalib
Memory Allocation 15
Array Allocation
An array also needs to have data allocated contiguously meaning
that if there is an array of boolean with 10 elements, these arrays
need to occupy 10 bytes, it can’t be broken. Otherwise, it would
impossible to have quick access to its elements.
Now, remember that the boolean type takes 1 bit but each memory
slot only stores 1 byte. The JVM (Java Memory Model) also avoids
word tearing which basically forces values not to be broken down
into the same memory slot. Also, word tearing should avoid
changing multiple fields in the same memory slot.
Therefore, each boolean value will hold 1 byte of space, other than
that, we will need 10 contiguous bytes to allocate an array of 10
boolean values. Internally the JVM will store more bytes for the
array. We don’t need to know this in detail but if you are curious
you can run your own tests with the library JOL (Java Object
Layout).
https://t.me/javalib
Memory Allocation 16
https://t.me/javalib
Memory Allocation 17
Summary
The main focus of this chapter is to show you how variables and
methods are stored in memory, not necessarily to show you how the
https://t.me/javalib
Memory Allocation 18
https://t.me/javalib
Big O Notation
The Big(O) Notation is a fundamental concept to help us measure
the time complexity and space complexity of the algorithm. In
other words, it helps us measure how performant and how much
storage and computer resources an algorithm uses.
Also, in any coding interview, you will be required to know Big(O)
notation. That will help you to get your dream job since every
FAANG (Meta, Alphabet, Amazon, Apple, Netflix, Microsoft,
Google) company will ask you that, even other companies.
The Big(O) notation is not 100% accurate, instead, it’s an estimate
of how much time and space your algorithm will take. Another
rule is that the Big(O) notation will be calculated considering the
worst-case scenario.
Now that we have some understanding regarding Big(O) notation,
let’s see the following diagram from the fastest O(1) to the slowest
O(n!) time complexity:
Source from (https://www.bigocheatsheet.com)[https://www.bigocheatsheet.com]
Asymptotic Notations
The asymptotic notation is a defined pattern to measure the perfor-
mance and memory usage of an algorithm. Even though the Omega
and Theta notations are not very much used in coding interviews,
it’s important to know they exist.
Let’s see what are those asymptotic notations:
https://t.me/javalib
Big O Notation 20
https://t.me/javalib
Big O Notation 21
Constant – O(1)
In practice, we need to measure the Big(O) notation by checking
the number of an elementary operation. In the case where we are
creating an int number in memory, we are storing 8 bytes. If we
were to be very precise with the Big(O) time complexity we would
have to write O(8). But notice that this is a constant time. It doesn’t
depend on any external number. It’s also irrelevant, O(8) doesn’t
really change much performance. For this reason, we can consider
that O(8) is actually O(1).
When assigning a variable this will take the time complexity of
O(1) because it’s only one operation. Remember that the Big
(O) notation will not measure precisely the performance of the
algorithm. Therefore, the O(1) time complexity is an abstract way
to measure code performance. Keep in mind that O(1) is pretty fast
though. It’s doing only one light operation:
int number = 7; // O(1) time and space complexity If we have
a code with many operations and we are not using any kind of
external parameter that will change the time complexity, it will still
be considered as O(1). Let’s see the following code:
Notice that even though the code above would be considered some-
thing around O(3), the number 3 is irrelevant because it doesn’t
https://t.me/javalib
Big O Notation 22
Logarithmic – O(log n)
The classic algorithm that uses the O(log n) complexity is the
binary search. That’s because it’s not necessary to run through
the whole array. Instead, we get the number in the middle and
check if it’s lower or greater than the number to be found. Then
we break the array in two, we break it again until the number is
found. That’s only possible because a binary search must have the
array already sorted.
Let’s see the following diagram:
https://t.me/javalib
Big O Notation 23
https://t.me/javalib
Big O Notation 24
https://t.me/javalib
Big O Notation 25
36 }
37 System.out.println(binarySearch(array, 1));
38 }
39 }
Linear – O(n)
When we use looping we have a linear complexity. It doesn’t matter
if it’s the ‘for’, ‘while’, ‘foreach’, ‘do while’. Any of those loopings
will have a linear complexity.
Notice that the above code will print the number i that is incre-
mented in each iteration. Since we run through the looping n times,
we will have the time complexity of O(n).
Another important point is that if we have two loopings that
depend on the same input, in our case, n we will still have the time
complexity of O(n).
Let’s see how that translates in code:
https://t.me/javalib
Big O Notation 26
As you can see in the above code, at first we might think that
the time complexity is more than O(n) but it’s actually still O(n)
because both loopings are using n as the size number.
https://t.me/javalib
Big O Notation 27
As you can see in the above code, we have two inputs, m and n.
Also, it doesn’t matter if the size of n is greater than m or vice-
versa, the Big(O) notation will always be O(m + n) because m or n
might be any number. Therefore, it might dramatically impact the
time complexity of either m or n.
https://t.me/javalib
Big O Notation 28
Quadratic – O(n²)
The exponential time complexity is present in low-performant
sorting algorithms such as bubble sort, Selection sort, and Insertion
sort.
https://t.me/javalib
Big O Notation 29
Output: 100
As you can see in the code above, the iteration depends on the size
of the array. We also have a for loop inside another one which
makes the time complexity be multiplied by 10 * 10.
Notice that the size of the array we are passing is 10. Therefore, the
countOperations variable will have a value of 100.
Cubic – O(n ^ 3)
The cubic complexity is similar to the quadratic one but instead of
having two nested loopings, we will have 3 nested loopings.
Let’s see how this will be represented in the code:
https://t.me/javalib
Big O Notation 30
Output: 1000
If we pass an array with the size of 10 to the above
countOperationsOn3 method, we will have an output of 1000.
That’s because 10 * 10 * 10 = 1000. Notice that the cubic
complexity will happen when we use 3 nested loopings. This time
complexity is very slow.
Exponential – O(c ^ n)
Exponential complexity is one of the worst ones. Also, to make the
notation clear, c stands for constant and n for the variable number.
Therefore, if we have the constant number of 10 ^ 5, we have 10
* 10 * 10 * 10 * 10 which corresponds to 100000.
https://t.me/javalib
Big O Notation 31
• Chocolate
• Strawberry
• Whipped Cream
Toppings Combinations
1 1 - Plain cake
2 4 - Plain cake, chocolate, strawberry,
chocolate & strawberry
3 8–…
As you can see in the table above, the number grows exponentially.
If we want a cake with 10 toppings, then the amount of combina-
tions would be 1024, and so on!
https://t.me/javalib
Big O Notation 32
Factorial O(n!)
The concept of factorial is simple. Suppose we have the factorial of
5! then this will equal 1 * 2 * 3 * 4 * 5 which results in 120.
Summary
Measuring time complexity and space complexity is an essential
skill for every software engineer who wants to create high-quality
software to stand out. In this chapter, we learned:
https://t.me/javalib
Big O Notation 33
https://t.me/javalib
Big O Notation 34
https://t.me/javalib
Array
The array data structure is probably the most used in every ap-
plication. If not directly used it’s indirectly used with ArrayList,
ArrayDeque, Vector, and other classes.
Simply put, an array is a data structure that stores multiple vari-
ables into it so that there is no need to create many variables with
different names.
Arrays in Java are always an object, therefore, they will occupy
space in the memory heap and it will create a reference for this
object.
https://t.me/javalib
Array 36
https://t.me/javalib
Array 37
Static Arrays
As the name suggests, a static array is an array that can’t be
changed. We need to pass the type and size of the array and after
that, we can’t change the type or size of the array.
Let’s see in the following code how to create a static array with the
int type and show all the elements:
Output: 1 2 3 4 5
Notice in the code above that we create an array of int values. Once
it’s created we can’t change either the type or the size of the array,
that’s what makes it static.
Then we access each element of the array by index and show
the values that will be 0 because those are the default values for
primitive int in Java.
https://t.me/javalib
Array 38
Dynamic Arrays
There are classes in Java that make use of a dynamic array such as
ArrayList, Vector, and others. When we create an ArrayList, we
have a static array under the hood that starts as empty but after
adding the first element the size goes to 10. Then it doubles every
time it’s needed as you can see in the following code of the JVM:
https://t.me/javalib
Array 39
The elementData variable is the one that will store the data behind
the scenes for the ArrayList. Therefore, notice that the dynamic
array actually manipulates a static array to behave as dynamic.
When adding an element to an ArrayList, it will check if the size is
greater than 10 and if that is true the time complexity will be O(n).
That happens because since the array was created with the size of
10, it’s necessary to create a new array with the size of 20 copying
all the elements into the new one. To do so, we need to traverse the
whole array and copy element by element. Then we add the 11th
element to the array.
When the array behind the scenes is created with the size of 20 then
whenever we add an element we will have the time complexity of
O(1). Notice that the vast majority of the time when adding an
element to a dynamic array will be pretty fast, it will be O(1). Only
on the edge-case scenarios when the array size needs to be doubled
the time complexity will be O(n). This is also called amortized
complexity.
https://t.me/javalib
Array 40
Summary
• An array is allocated in memory from back to back.
• Accessing an array by index has the time complexity of O(1),
it’s pretty fast.
• Static array is the array that is created with a size and a type
pre-defined.
• Dynamic array is an adaptation of the static array that
automatically resizes it when necessary.
• A dynamic array will double its size when necessary.
• Adding an element to a dynamic array will be mostly O(1)
because there will be space more often.
• When adding an element to a dynamic array exceeds the size
of the static array under the hood, it will be necessary to
create a new array, copy the elements from the existing array,
then add the new element. Therefore, the time complexity
will be O(n).
• Adding an element in the middle of the array will have the
time complexity of O(n). That’s because it will be necessary
to shift all the elements from the right side to one position on
the right. Only then we will be able to insert the element by
index.
• To remove the first element from the array, it will be nec-
essary to shift all the elements from the right to the left.
Therefore, the time complexity is O(n).
• To remove an element from the array in the last position takes
O(1) complexity. That’s because we only need to remove the
last value and we have direct access to it.
https://t.me/javalib
String
The String data structure in Java and in other programming lan-
guages behind the scenes is an array of bytes. Bytes behind
the scenes are numbers that can be translated into bits or binary
numbers. Those numbers correspond to a character in an encoding
standard called ASCII (American Standard Code for Information
Exchange) as you can see in the following diagram.
** ASCII Codes **
https://t.me/javalib
String 42
https://t.me/javalib
String 43
Output: A:65 B:66 C:67 D:68 a:97 b:98 c:99 d:100 1:49 2:50 3:51 4:52
https://t.me/javalib
String 44
Output: N
Copy a String
In programming languages like C++ a String is mutable. In other
programming languages such as Java, Python, C#, Kotlin, and
Golang a String is immutable. This means that those Strings will
not be changed in memory. Therefore, every time we concatenate
a String via code, another String will be created.
This means that whenever we concatenate a String, we will have
the time complexity of O(N). That happens because a new array of
bytes will be created in memory and then will copy the String array
into it.
Let’s see the following code example:
https://t.me/javalib
String 45
Code analysis:
Depending on the n parameter, there will be more concatenations.
Also, notice that there is a looping with n and within that loop
https://t.me/javalib
String 46
https://t.me/javalib
String 47
Output (The output will slightly vary from time to time): 2820
milliseconds 5 milliseconds
As you can see in the code above the difference in time processing is
exponential. By using the StringBuilder to concatenate String when
doing the looping 100000 times, the StringBuilder will perform 564
times faster than normal concatenation.
That’s why it’s so important to understand what happens behind
the scenes with data structures and code, otherwise, we won’t be
able to understand why the performance is extremely better with
StringBuilder.
Another important class to know about is StringBuffer that has
does the same as StringBuilder but is thread-safe, meaning that will
avoid data collision when working in a multi-thread environment.
Time complexity is another crucial concept to understand to create
performant code.
https://t.me/javalib
Hashtable 51
Code analysis:
In the code above we create a new node with the passed value, then
we set this value to the next pointer from the tail.
Notice that the tail (last element) at this moment holds the same
object reference from the head (first element). That’s the reason
the objects will be chained correctly.
Then, we add the element to the tail object since this is the last
object from the Linked List.
Notice in the code above that the add operation in a Linked List is
pretty simple. Therefore, the time complexity will be O(1) since we
only do a value assignment. There is no need to traverse the Linked
List.
What is Queue?
We can use an analogy of a real-world queue to explain what is a
queue in computer science. Let’s imagine a person that goes to a
bank queue to pay a bill. The first person to arrive in the queue is
the first person out or the first person to be served. The last person
arriving in the queue will be the last one to be served.
Let’s see the diagram:
https://t.me/javalib
Queue 86
1 import java.util.LinkedList;
2 import java.util.Queue;
3
4 public class QueueExample {
5
6 public static void main(String[] args) {
7 Queue<String> peopleQueue = new LinkedList<>();
8 peopleQueue.add("Wolverine"); // First in & first out
9 peopleQueue.add("Juggernaut");
10 peopleQueue.add("Xavier");
https://t.me/javalib
Queue 87
https://t.me/javalib
Queue 88
https://t.me/javalib
Queue 89
https://t.me/javalib
Queue 90
1 import java.util.Deque;
2 import java.util.LinkedList;
3
4 public class DequeDeleteFirstAndLast {
5 public static void main(String[] args) {
6 Deque<String> xmenQueue = new LinkedList<>();
7 xmenQueue.add("Wolverine");
8 xmenQueue.add("Cyclops");
9 xmenQueue.add("Xavier");
10
11 System.out.println("Removing first: " + xmenQueue.rem\
12 oveFirst());
13 System.out.println("Removing last: " + xmenQueue.remo\
14 veLast());
15
16 showAndRemoveQueueElements(xmenQueue);
17 }
18
19 private static void showAndRemoveQueueElements(Deque<St\
20 ring> peopleQueue) {
21 var queueSize = peopleQueue.size();
22 for (int i = 0; i < queueSize; i++) {
23 System.out.print(peopleQueue.poll() + " ");
24 }
25 }
26 }
https://t.me/javalib
Queue 91
https://t.me/javalib
Queue 92
1 void linkLast(E e) {
2 final Node<E> l = last;
3 final Node<E> newNode = new Node<>(l, e, null);
4 last = newNode;
5 if (l == null)
6 first = newNode;
7 else
8 l.next = newNode;
9 size++;
10 modCount++;
11 }
Find an element
The time complexity is O(n) and that’s because until the element is
found it’s necessary to traverse the graph, element by element.
https://t.me/javalib
Queue 93
1 private E unlinkFirst(Node<E> f) {
2 // assert f == first && f != null;
3 final E element = f.item;
4 final Node<E> next = f.next;
5 f.item = null;
6 f.next = null; // help GC
7 first = next;
8 if (next == null)
9 last = null;
10 else
11 next.prev = null;
12 size--;
13 modCount++;
14 return element;
15 }
Delete the last element of the queue It’s only possible to delete the
last element by using a double-linked list. Also, since a double-
linked list stores the last element into a separate variable, we have
direct access to it to perform the deletion. Therefore, the time
complexity is O(1).
To briefly explain the following code, we receive the last element by
parameter, variable “l”. Then we pass the previous element attached
to the last element to a new variable. We pass null to item and prev
from the last element to help the garbage collector collect those
dead instances. Finally, we pass the reference from prev to the last
element instance variable.
https://t.me/javalib
Queue 94
1 private E unlinkLast(Node<E> l) {
2 // assert l == last && l != null;
3 final E element = l.item;
4 final Node<E> prev = l.prev;
5 l.item = null;
6 l.prev = null; // help GC
7 last = prev;
8 if (prev == null)
9 first = null;
10 else
11 prev.next = null;
12 size--;
13 modCount++;
14 return element;
15 }
Summary
The queue data structure is a very important data structure to be
mastered and it’s used in the famous breadth-first search algorithm.
Let’s see the key points from the queue data structure:
It uses the FIFO (First-in First-out) structure. The same from a
real-world queue. To insert and delete at the beginning or at the
end of the queue the time complexity is O(1). To find an element
in a queue with a LinkedList the time complexity is O(n) because
it’s necessary to traverse all elements for the worst-case scenario.
To delete or insert an element in the middle of the queue the time
complexity is also O(n).
https://t.me/javalib
Graph
The graph data structure is a composition of nodes connected by
edges. Graphs are vastly used in the real world. One very simple
example is Facebook where a person is a friend of another person
and so on. Graphs can also represent routes from one place to
another.
A graph has nodes/vertices and is connected by the edges. To
exemplify those nomenclatures, let’s see the following image:
https://t.me/javalib
Graph 96
https://t.me/javalib
Graph 97
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class Node {
5
6 Object value;
7 private List<Node> adjacentNodes = new ArrayList<>();
8
9 public Node(Object value) {
10 this.value = value;
11 }
12
13 public void addAdjacentNode(Node node) {
14 this.adjacentNodes.add(node);
15 }
16
17 public List<Node> getAdjacentNodes() {
18 return adjacentNodes;
19 }
20
21 public void showNodes() {
22 BreathFirstSearchPrintNodes.printNodes(this);
23 }
24
25 public Object getValue() {
26 return value;
27 }
28 }
https://t.me/javalib
Graph 98
Undirected Graph
In the example of Facebook, a person is a friend of another one,
therefore, this connection is unidirectional. Let’s see how this can
be represented in the following image:
Notice in the graph above that Rafael is a friend of Bruno and
the connection is undirected. This means that Rafael has access
to Bruno’s profile and vice-versa.
Let’s see that in code in the following example. Let’s use the
same Node class as above but we will explore the connect method
instead:
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class Node {
5
6 Object value;
7 private List<Node> adjacentNodes = new ArrayList<>();
8
9 // Omitted addAdjacentNode, getAdjacentNodes, showNodes\
10 , and getValue methods...
11 public void connect(Node node) {
12 if (this == node) throw new IllegalArgumentException(\
13 "Can't connect node to itself");
https://t.me/javalib
Graph 99
14 this.adjacentNodes.add(node);
15 node.adjacentNodes.add(this);
16 }
17
18 }
https://t.me/javalib
Graph 100
Directed Graph
The graph data structure can be directional, which means that it
might connect to one node but the other node might not connect
back. A simple real-world example of that is when an airplane
goes somewhere. The airplane will go to another city, therefore,
it’s directional.
Another example is Twitter, a person can follow another one.
However, the other person doesn’t need to follow back.
Let’s see how that works in the example of Twitter:
Notice in the image above that Rafael follows James Gosling, Duke,
John, and Bruno. However, James Gosling, Juggy, Duke, and John
don’t follow Rafael back. Rafael follows Bruno and Bruno follows
back Rafael. From the node of Rafael, it’s possible to traverse
through the whole graph.
Also, notice that the graph above doesn’t have cycles. Therefore,
it’s an acyclic graph.
Let’s see how to represent the above graph in code:
https://t.me/javalib
Graph 101
https://t.me/javalib
Graph 102
https://t.me/javalib
Graph 103
https://t.me/javalib
Graph 104
11 dukeNode.showNodes();
12 }
13 }
Summary
The Graph data structure is highly important to be mastered by
every software developer. It’s vastly used behind the scenes by
frameworks, libraries, and technologies. That’s the reason why it’s
vastly asked in many companies during interviews.
To recap, let’s see the important points:
https://t.me/javalib
Tree
The tree data structure is a type of graph. A tree has a root node (top
node) that will have a relationship with its child nodes. The path
that connects the root node to the child nodes is called a branch.
The leaf node is the node that doesn’t have any children and is not
the root node.
In algorithms, you will see a lot of the nomenclature height. Height
in trees is the number of nodes from the highest branch to the root
node. Another keyword is the depth of a tree which means the
count of nodes from a specific node to the root node.
To make those nomenclatures clear, let’s see them in a diagram:
https://t.me/javalib
Tree 106
https://t.me/javalib
Tree 107
1 import java.util.LinkedList;
2 import java.util.List;
3
4 public class TreeNode {
5
6 private String value;
7 private List<TreeNode> childNodes;
8
9 public TreeNode(String value) {
10 this.value = value;
11 this.childNodes = new LinkedList<>();
12 }
13
14 public void addChild(TreeNode childNode) {
15 this.childNodes.add(childNode);
16 }
17
18 public void showTreeNodes() {
19 BreathFirstSearchPrintTreeNodes.printNodes(this);
20 }
21
22 public String getValue() {
23 return value;
24 }
25
26 public List<TreeNode> getChildNodes() {
27 return childNodes;
28 }
29 }
Notice that the code above is very similar to the graph data
structure. We are also using the Breadth-first search algorithm to
show the data from the tree. For now, don’t worry about it, just
keep in mind that this is a famous algorithm that will visit and
print each node. Now let’s populate the data from the company
https://t.me/javalib
Tree 108
https://t.me/javalib
Tree 109
Binary Tree
A binary tree is a tree that has up to 2 child nodes. Let’s see how
that works in a diagram:
Notice that the diagram above is a binary tree because the nodes
have up to 2 children at max. Even though node 3 has only one
child that still makes the above diagram a binary tree.
https://t.me/javalib
Tree 110
Ternary Tree
A ternary tree is similar to the binary tree but instead of having up
to 2 child nodes, it has up to 3 child nodes. Let’s see how this is
represented in a diagram:
Notice in the diagram above that node 1 and node 2 has 3 child
nodes. Even though node 4 has only one child node this also doesn’t
matter, it’s still a ternary tree since the max number of child nodes
is 3.
K-ary tree
The “K” represents the max number of child nodes a tree can have.
For example, we can represent a binary tree as a 2-ary tree because
https://t.me/javalib
Tree 111
both mean that the tree can have up to 2 child nodes. Similarly, a
3-ary tree is the same as a ternary tree.
https://t.me/javalib
Tree 112
Remember that if the binary tree is complete only for the rightmost
nodes, that wouldn’t be considered a complete binary tree.
https://t.me/javalib
Tree 113
https://t.me/javalib
Tree 114
Notice in the diagram above that the total height of this tree is 3.
https://t.me/javalib
Tree 115
https://t.me/javalib
Tree 116
https://t.me/javalib
Tree 117
the height of the nodes is higher than 1 and for this reason, the tree
above is not a balanced tree.
Summary
In this chapter, we saw essential concepts from the Tree data
structure. As you can see, a tree is a graph used in many systems
and the real world. Let’s see the key points from the Tree data
structure:
https://t.me/javalib
Recursion
Recursion is a programming fundamental that is highly used in
algorithms. Simply put, recursion is the ability of a method to call
itself. When a method calls itself it stacks up and uses the LIFO
(Last-in First-out) approach. It’s the same concept as a stack of
plates. Let’s see the following scenario:
To summarize the above figure, remember that the last element that
is inserted in the stack will be the first one to be removed. That’s
why LIFO.
To memorize and really understand the LIFO approach, just re-
member that when you stack up plates, you can’t get the plate at
the bottom. Instead, it’s much easier and more suitable to get the
first one at the top of the stack. The methods are stacked exactly in
the same way!
FILO (First-in Last-out) has the same result as LIFO. It basically
means that the first plate to go on the stack will be the last one to
go out.
https://t.me/javalib
Recursion 119
To summarize the above figure, the first plate that is inserted in the
stack will be the last one to be removed.
Another point to keep in mind is that what can be done with
recursion can also be done with loopings too and vice-versa.
Let’s see a simple Java example using this concept:
Code analysis:
https://t.me/javalib
Recursion 120
https://t.me/javalib
Recursion 121
Output: 4 3 2 1 0
Notice that the number was printed in reverse order. That’s because
the method is recursively stacked as you can see in the debugging
images:
When all methods were invoked, notice that the variable number
is 5:
When all methods were invoked and the methods are going out of
the stack the variable number is 3:
https://t.me/javalib
Recursion 122
Therefore, it’s like the recursive call will intercept this method
invocation and wait until the last recursive method is called to be
the first out of the stack. That’s why the first number to be printed
is 4!
Pros:
• Merge sort has a time complexity of O(n log n), which means
that it can sort large amounts of data efficiently.
• It is a stable sorting algorithm, meaning that it preserves the
relative order of equal elements in the input list.
• Merge sort is a divide-and-conquer algorithm, which makes
it easy to implement recursively and understand.
• Merge sort can also be used to efficiently merge two sorted
lists into a single sorted list.
Cons:
https://t.me/javalib
Merge Sort 161
Summary
The merge sort is not an easy one to master. However, let’s see
some essential points of this algorithm so you can more easily
remember and master it.
The merge sort algorithm:
https://t.me/javalib
Depth-first-search
The depth-first-search algorithm is used a lot in algorithms where
it’s necessary to traverse through nodes. This algorithm is likely
not to be used in day-to-day work directly. However, LinkedList
uses the concepts of graphs, so we use it indirectly all the time.
Before going through the DFS (Depth-First-Search) algorithm, en-
sure you fully understand the graph data structure, tree, stack data
structure, and recursion.
Let’s use the following graph to traverse using the
depth-first-search algorithm.
https://t.me/javalib
Depth-first-search 163
https://t.me/javalib
Depth-first-search 164
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class Node {
5
6 Object value;
7 private List<> adjacentNodes = new ArrayList<>();
8 private boolean visited;
9
10 public Node(Object value) {
11 this.value = value;
12 }
13
14 public void addAdjacentNode(Node node) {
15 this.adjacentNodes.add(node);
16 }
17
18 public List<> getAdjacentNodes() {
19 return adjacentNodes;
20 }
21
22
23 public Object getValue() {
24 visited = true;
25 return value;
26 }
27
28 public boolean isVisited() {
29 return visited;
30 }
31 }
Now let’s populate this graph with the same data from the diagram
above:
https://t.me/javalib
Depth-first-search 165
Preorder Traversal
The preorder graph traversal means that we will start from the root,
then will traverse to the left and right subtrees.
https://t.me/javalib
Depth-first-search 166
Output: 1 2 3 4 5 6 7
Notice in the code above that we first ask if the node was not
visited and then invoke the dfsRecursiveWithoutLambda method
recursively. The methods are stacked into the memory heap. The
LIFO (Last-in First-Out) approach is used. This means that the last
invoked method will be the first to be fully executed.
https://t.me/javalib
Depth-first-search 167
1 import fundamentals.graph.Node;
2
3 public class DepthFirstSearchPreorder {
4
5 public static void main(String[] args) {
6 Node rootNode = GraphMock.createPreorderGraphMock();
7 dfsRecursive(rootNode);
8 }
9
10 public static void dfsRecursive(Node node) {
11 System.out.print(node.getValue() + " ");
12 node.getAdjacentNodes().stream()
13 .filter(n -> !n.isVisited())
14 .forEach(DepthFirstSearchPreorder::dfsRecursive);
15 }
16
17 }
Output: 1 2 3 4 5 6 7
As you can see in the code above, we are using the recursion
technique. We traverse through the adjacent (neighbor) nodes
filtering the nodes that were already visited. We are also invoking
the dfsRecursive method recursively which will basically stack up
the methods in a stack and will pop them out as soon as there isn’t
any other adjacent node to be traversed.
https://t.me/javalib
Depth-first-search 168
} } Output: 1 2 3 4 5 6 7
Notice that the code above is not very intuitive. We have to iterate
the child elements of each node in the reverse order and then put
the elements into a stack. However, it’s important to show this
code to prove that what can be done recursively can also be done
iteratively.
Postorder Traversal
The postorder traversal will start from the leaf nodes in the left
subtree until all the left subtree nodes are visited. Then the same
will happen in the right subtree, and finally, the root will be printed.
https://t.me/javalib
Depth-first-search 169
Output: 5 4 3 2 7 6 1
https://t.me/javalib
Depth-first-search 170
Output: 5 4 3 2 7 6 1
https://t.me/javalib
Depth-first-search 171
Code analysis:
Notice in the code above that we will only show the node’s data
if it’s the leaf node. Which is exactly what we need. Another
important point is that we insert data into the stack first from a
right node and then from the left node.
The stack will push the inserted node as the first element of the
stack. Since in the postorder traversal we want to print first the
left subtree, that will work perfectly. Another crucial detail here is
that we are changing the state of the right and left nodes and that
must be done! If we don’t change the state of those nodes the nodes
coming before the leaf node will never be printed. That’s because
we only print leaf nodes!
If we don’t want to change the state of our nodes, the code would
be a little bit more complicated. But it’s not really necessary to
know all the types of algorithms with graphs. We need to know
the principles instead to be able to understand and solve common
algorithms we might face in an interview or even on day-to-day
tasks.
Inorder Traversal
The inorder traversal will first print the left subtree, then the root,
and finally the right subtree.
https://t.me/javalib
Depth-first-search 172
Output: 3 2 4 5 1 6 7
Code analysis:
In the code above, we first have to check if the node is different than
null to avoid a NullPointerException. Then we traverse the left
nodes first and then methods are stacked up recursively. Let’s see
in detail what happens when invoking those methods recursively.
We start from the root, which is node 1. Then we invoke the method
recursively to the left node, then we are in node 2. Then we invoke
again the method recursively with the left node which is 3 now.
But node 3 doesn’t have either left or right nodes, therefore, the
base condition from the recursive method is fulfilled because the
next left or right node will be null.
Now the methods will start being popped out from the stack. The
first one to be popped out is node 3. Now we go back to node 2.
In node 2 we will continue the method execution to print the value
of 2 and then will invoke the right node. The right node from 2
is node 4. Then another recursive call is made with node 4 firstly
https://t.me/javalib
Depth-first-search 173
trying to invoke the left node but this one will be null. Therefore,
the method execution will continue.
In node 4 we only have the right node. Therefore, the number 4 will
be printed and the right 5 is invoked. Node 5 doesn’t have a left or
right node. Therefore the method from node 5 will be popped out
of the stack and the number 5 will be printed.
Then, the method invocation will go back to the root node and
pretty much the same on the right side.
To summarize this process, let’s see the order in the nodes are
invoked and printed:
1 1 -> 2 -> 3 -> null -> prints 3 -> null -> prints 2 -> 4 \
2 -> null -> prints 4 -> prints 5 -> null -> 5 -> prints 1
3 -> prints 6 -> null -> 6 -> prints 7 -> null -> 7
https://t.me/javalib
Depth-first-search 174
11
12 TreeNode lastNode = stack.pop();
13 System.out.print(lastNode.value + " ");
14 current = lastNode.right;
15 }
16 }
Output: 3 2 4 5 1 6 7
Code analysis:
Notice that the code above has a similar idea to the recursive
algorithm. First, we populate the stack with the left nodes. Then,
we print the latest node from the left and search for the right node.
If the right node is null, then the previously inserted elements from
the stack will be popped out and printed.
Remember that in the in-order traversal, the left leaf nodes must be
printed first. That’s why the elements from the left are stacked up
first too.
https://t.me/javalib
Depth-first-search 175
Summary
The depth-first-search algorithm will visit the depth nodes first
and then the nodes closer to the root. As we’ve seen we can use
this algorithm for graphs or tree data structures.
We can use the preorder, postorder, and in-order, algorithms to
traverse graphs. Notice also that it’s much easier to use the depth-
first-search algorithms with a binary tree instead of a graph that
might be cyclic or have more than two children nodes. Therefore,
let’s recap the main points:
https://t.me/javalib
Breadth-First Search
The Breadth-First search algorithm is a way to traverse through
graphs or trees so that the nodes are visited level by level. The
depth-first search algorithm, on the other hand, will traverse nodes
to the depth-first as its name suggests, in other words, branch by
branch.
The traversal starts from the root node, in the case of the following
diagram, from the top. Node 1 will be printed first (level 1), then
node 2 and node 3 (level 2), then node 4, node 5, and node 6
(level 3), and finally, node 7 and node 8 (level 4).
https://t.me/javalib
Breadth-First Search 177
https://t.me/javalib
Breadth-First Search 178
Let’s populate the above tree with the same data from the diagram
we’ve seen above:
https://t.me/javalib
Breadth-First Search 179
13 rootTreeNode.right = treeNode3;
14
15 treeNode2.left = treeNode4;
16 treeNode2.right = treeNode5;
17 treeNode3.right = treeNode6;
18
19 treeNode5.left = treeNode7;
20 treeNode5.right = treeNode8;
21
22 return rootTreeNode;
23 }
24 }
https://t.me/javalib
Breadth-First Search 180
1 import java.util.LinkedList;
2 import java.util.Queue;
3
4 public class BreathFirstSearch {
5
6 public static void main(String[] args) {
7 bfsForTree(TreeMock.createBfsMock());
8 }
9
10 public static void bfsForTree(TreeNode node) {
11 Queue<TreeNode> queue = new LinkedList<>();
12 queue.add(node);
13
14 while (!queue.isEmpty()) {
15 var currentNode = queue.poll();
16
17 if (currentNode != null) {
18 System.out.print(currentNode.value + " ");
19 queue.add(currentNode.left);
20 queue.add(currentNode.right);
21 }
22 }
23 }
24
25 }
Output: 1 2 3 4 5 6 7 8
https://t.me/javalib
Breadth-First Search 181
Now let’s see in code how we describe a Graph and how to populate
it as the above diagram in code:
https://t.me/javalib
Breadth-First Search 182
https://t.me/javalib
Breadth-First Search 183
1 import java.util.ArrayDeque;
2 import java.util.Queue;
3
4 public class BreathFirstSearch {
5
6 public static void main(String[] args) {
7 bfsForGraph(GraphMock.createBfsMock());
8 }
9
10 public static void bfsForGraph(Node node) {
11 Queue<Node> queue = new LinkedList<>();
12 queue.add(node);
13
14 while (!queue.isEmpty()) {
15 var currentNode = queue.poll();
16 if (!currentNode.isVisited()) {
17 System.out.print(currentNode.getValue() + " ");
18 queue.addAll(currentNode.getAdjacentNodes());
19 }
20 }
21 }
22
23 }
Output: 1 2 3 4 5 6 7 8
https://t.me/javalib
Breadth-First Search 184
Summary
– The Breadth-first Search algorithm uses a queue FIFO (First-
in First-out) approach. – The nodes are visited level by level. –
There is no need to check if the node was visited when traversing
a tree since a tree can’t be cyclic. – When we traverse a graph, it’s
necessary to check if the node was already visited, otherwise, there
will be an infinite loop.
https://t.me/javalib
Next Steps
Now that you have a strong foundation with fundamentals, it’s
time to try interviews. Also, make sure you will practice algo-
rithms. Once you know the fundamentals it’s much easier to solve
them.
Those are the websites I recommend you to try out:
https://www.algoexpert.io https://leetcode.com https:
//www.hackerrank.com
Make sure your CV is aligned to what the market wants because
that will make the difference if they will call you for the interview
or not.
Don’t get intimidated by the amount of technologies you need to
study also, instead, try out the interview and see how it goes.
https://t.me/javalib
Next Steps 186
Be Risk Taker
Paradoxically, taking risks is much safer than not taking risks at all.
Imagine a developer who stays in the same company for more than
10 years and only uses old technologies. Then, suddenly his boss
reaches out to him and get him fired.
Therefore, because the developer didn’t go out of his comfort zone,
now he has to face the market with skills that are obsolete and he
will be looking for jobs without a job which is not a good position.
If the developer had tried to look other companies and had some
interviews he would know what the market was asking. But
because he didn’t take any riks, now he has to do lots of interviews
without preparation.
Remember, always take calculated risks, don’t be the accomodated
developer. Be an action taker instead!
https://t.me/javalib
Next Steps 187
Interview Mindset
To pass in interviews and don’t give up, you need to have a
calibrated mindset. You need to be resilient, be able to receive
feedback and not allow your ego take control of yourself.
Many developers get frustrated, feel they are not enough, or feel
they are not even worthy of being a software engineer after being
rejected in interviews.
If you feel like that or you simply want to go to the next level faster
I strongly suggest you to take the next step and have Challenger
Developer course so you empower yourself to get the best Java jobs
with high salary.
Remember, the best investment you can ever make is to invest
in yourself. Also, it’s the action taker the one who will make a
difference.
Conclusion
That’s all challenger, I am happy to see you finishing this book, I
know it’s not an easy book but it’s those books who will make you
go to the next level.
Also, adopt the mindset of never stop learning, remember, we
need to be growing constantly and this is fun! Just be careful to not
learn too much and never use the knowledge. Learn something so
can you do something. Otherwise, it’s a waste of your time.
Take your next step and do the Challenger Developer course so you
go to the next level in your career! https://javachallengers.com/
challenger-developer-course
https://t.me/javalib