Showing posts with label start-here. Show all posts
Showing posts with label start-here. Show all posts

Tuesday, July 1, 2025

Binary and Hex basics

Every good programmer needs to know—of course—how to count and do basic math in the decimal system with digits 0123456789. But good programmers should also know how to count and do basic math in two other bases: binary and hexadecimal. The two are related, and it's especially easy to go between them, so we’ll cover conversions before we wrap up here.

First, binary. We normally count with 0123456789, so we have 10 symbols, and our numbers revolve around powers of 10: 1 is 1, 10 is ten times more; 100 is ten times more than that; 1000 is ten times more still; etc. On the other hand, 0.1 is one tenth of 1; 0.01 is one tenth of 0.1; 0.001 is one tenth of 0.01; and so on.

Binary reduces the symbol load to only two—01. Anything you can do in decimal, you can do in binary, and vice versa. The only difference is that place values aren’t 1s, 10s, 100s, 1000s, etc.—they’re 1s, 2s, 4s, 8s, 16s, etc. So binary numbers roll over to more places sooner than base 10 does (about 3.32 times faster), but how the numbers behave when counting, adding, multiplying, dividing, and subtracting is exactly the same.

And just like 9,999,999 is one less than 10,000,000 (which is a perfect power of 10), the equivalent is that 1111 1111 is one less than 1 0000 0000, which is a perfect power of 2. (In decimal, that’s equivalent to saying that 255 is one less than 256, by the way.)

Here's how I like to convert numbers from decimal to binary. Let’s convert 485.

  • I happen to know the biggest power of 2 less than or equal to 485 is 256 (which is 2*2*2*2… 8 times, or 2^8)
  • Subtract that number from 485, and we’re left with 229.
    • We have 1 grouping of 256, plus 229
  • Now, look at the biggest power of 2 less than 229. That’s 128. Make a note. Subtracting it off leaves 101.
    • 1 group of 256
    • 1 group of 128
  • Now, the biggest power left is 64, and subtracting it off leaves 37.
    • 1 group of 256
    • 1 group of 128
    • 1 group of 64
  • Now do the same for 37. The biggest power is 32. Subtracting it leaves 5.
    • 1 group of 256
    • 1 group of 128
    • 1 group of 64
    • 1 group of 32
  • 5 is smaller than 16 and 8, so we won’t have any groups of those
    • 1 group of 256
    • 1 group of 128
    • 1 group of 64
    • 1 group of 32
    • 0 groups of 16
    • 0 groups of 8
  • But we will have 1 group of 4, and that leaves us with 1 left
    • 1 group of 256
    • 1 group of 128
    • 1 group of 64
    • 1 group of 32
    • 0 groups of 16
    • 0 groups of 8
    • 1 group of 4
    • 0 groups of 2
    • 1 group of 1

We then write the number by converting the vertical list into a horizontal one: 357 in binary is 1 1110 0101. Checking our math is very easy: we can simply add all the powers where we have 1s, and ignore the powers where we have 0s, and the sum should be the number we started with. 256+64+32+4+1 is, in fact, 357, so we did everything right. Notice something: because we only have 2 digits, we can immediately know if our answer is wrong if the parity does not match as expected. If a supposedly even number comes back with a last digit of 1, we did something wrong. If a supposedly odd number comes back with a last digit of 0, we did something wrong.

Adding and subtracting work much the same way as in decimal, only there are more frequent carries or borrowings, since there are fewer symbols.

Hexadecimal uses 16 symbols, as opposed to just 10. In addition to 0123456789, we also use ABCDEF.

So counting in hexadecimal looks like 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12…, and that is equivalent in decimal to 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18…

Hexadecimal-binary conversions are nice because 16 is a power of 2, so the mapping between the bases is really easy: much, much easier than either binary or hexadecimal to decimal, or vice versa.

You’ll notice that, earlier in the article, I split up a binary number like 1 0110 0101. This makes seeing things in 4-bit groupings (as opposed to 3 decimal-digits groupings of thousands together, millions, billions, etc.) so much easier, and lets the hexadecimal practically jump off the page.

The conversion becomes almost trivial:

  • Starting from the rightmost place, break up the number into 4-bit segments, as done above: 1 1110 0101.
  • If the left-most segment (the most significant) is less than 4 bits, pad it on the left with zeroes: 0001 1110 010
  • Now, just treat every 4-bit section as a single hex digit—because it is—and convert it, remembering that A is decimal 10, B is decimal 11, C is decimal 12, D is decimal 13, E is decimal 14, and F is decimal 15

·       The number becomes:

  • Left-most group: 0001 in binary is 1 in hex
  • Second group: 1110 in binary is E in hex (because 1110 is 14 in decimal, which is E in hex)
  • Third group: 0101 in binary is 5 in hex

·       So the final number is 1E5 in hexadecimal

To convert this back into decimal, we know that our columns now are 1s, 16s, 256s, etc., so we have

  • 5 1s
  • E 16s (that is, 14 16s), or 224 in this column
  • 1 256

And the sum of all that (5 + 224 + 256) is in fact our original number of 485.

 

Tuesday, June 10, 2025

Java is hard. Trust me-- I know.

 Before we continue with more Java, I just wanted to take a quick break today to acknowledge how difficult programming (in Java or any language) can be and how frustrating it can be, especially at the beginning. (But, spoiler alert, the frustration doesn’t go away as you get more experienced; early problems get much easier, but harder problems can still be just as frustrating!)

You’ll write a lot of code on your way to Java proficiency—many thousands of lines. If you’re following along, learning from me, then you’re just getting started. People who do this for a full career from early adulthood to retirement will end up writing hundreds of thousands of lines over all those years.

And in all those years writing all those lines, there will be lots of bugs. If your lifetime output is 500,000 lines, you probably wrote at least 1,000,000 bugs along the way.  Brady, Manning, Montana, Brees, etc., are some of the greatest quarterbacks ever, and none of them has a career completion percentage over 70; each of them, as great as they were, made thousands of mistakes over their careers. Even the best make mistakes! That's OK! You will, but you'll improve over time! 

The more practice you get with the simple parts, the easier it is for those to be bug-free. There will be a point, as a programmer, where certain structures become so obvious that they become second nature. If you aren’t at that point yet, don’t worry! Bugs are a critical part of the learning process. (It took me an embarrassingly long time, for example, in 2017-18, to consistently remember to end my lines with a ;, and that’s a serious enough bug that your code will not run if even one of them is missing.)

Bugs come in two general flavors:

  • Errors in syntax
  • Errors in logic

Syntax errors, generally, are easier to grow out of: “oops, I called an int a ‘num’ because I forgot the name of the data type” or “oops, I left off a semicolon, or didn’t pair () inside () inside () correctly so there was one too many (”. These come down to learning the conventions of the language and being careful, just like a college professor makes fewer spelling mistakes than her 5-year-old kindergartener. The professor has been reading and writing far longer, so she has seen much more correct spelling and had many more opportunities to practice it.

Logic errors, on the other hand, are the real nasty bugs—the reason people in coding make so much: because it’s our job to work through an algorithm to do something, put it on paper, explain it to a coworker (this is great at catching these bugs), and then translate plain English into computable Java. If a problem is just particularly hard, or if your boss’s requirements aren’t clear, or if you’re working with something new, or if you’ve had a bad day, you’re much more likely to make logic errors, even after 10, 20, 30 years of coding. That is to say: professionals write bugs all the time, so if you’re just getting started, don’t beat yourself up!

But at the same time, don’t look for the easy way out. If you’re learning Hanoi, don’t Google the solution, or read too far down my article without playing with plates first. If you have an infinite loop or StackOverflowException somewhere, don’t throw your hands up and give the problem to an LLM to fix for you. It’s totally normal, especially early, to feel the pressure to get things right, right away—but this does not (or should not) exist. You will, and you can, make mistakes. You are learning!

If you're learning a new algorithm and you come across a bug, then the best things to do are any of the following:

  • Take a break
  • Ask questions 
  • Find a good explainer video/article
  • Ask someone else for help
  • Try to teach someone the algorithm and answer their questions
  • Make your learning experience multi-sensory somehow
But DON'T:
  • Go to an LLM right away for a quick and easy answer
  • Copy a solution from somewhere like StackOverflow or ChatGPT
  • Give up
  • Ask someone else to write it for you

Tuesday, June 3, 2025

The Tower of Hanoi: a Classic Problem

You’ve now been thinking about recursion for a few days, so it’s a good time to introduce one of the classic problems in early-education computer science: the Tower of Hanoi. Now, fair warning. When I learned the solution, about 2 years ago, it took me days of struggle and hours of literally playing with plates from the kitchen to figure it out. I will give the solution, but I cannot stress enough how important it is that you not read it until you too, have played with your own plates and come to it yourself. On a per-line basis, I think this was the code that was most thought-dense; that is, that required the most time thinking per line written, that I have ever written. 

The story is the following. You’re a priest in a temple in Hanoi, and there are 3 giant pillars in the courtyard. On one of those disks is some number of increasingly sized disks as you work toward the base: the higher you go, the smaller the disks, and the lower you go, the bigger the disks. 

Your task is the following:
  • Given where the pile is now, move it all to another (specified) rod in such a way that
  • You only move one disk at a time and
  • No bigger disk ever is on top of a smaller one and 
  • Hopefully you get there in as few moves as possible because there’s a really important festival that cannot start until you’ve finished moving the temple’s disks

After factorials and Fibonacci, this is one of the most classic, basic recursive problems out there. Because it’s so basic, there are hundreds, thousands of videos and articles out there explaining the solution (and this is another). Resist the temptation to consult them until after you’ve found the solution yourself. 

The solution comes in two parts:
  • What pseudocode solves the problem? And
  • What sequence of numbers counts the number of steps it takes, as the number of disks changes? 
The most important problem-solving skill in the beginning of tackling a problem like Hanoi is knowing how to reduce a problem to something as simple as possible. You are guaranteed to have at least one disk, so what’s the simplest problem? One disk, of course!

All the screenshots here are from Tower of Hanoi | Math Playground. I encourage you to use it (or other games like it; there are plenty online) to work through the solution. But really, if you have them, literal physical dinner plates of various sizes are even better. 
Now, the game state is such that there’s one disk on A. I want it to go to B. 
 
I can move it from A to B directly without violating any of the rules, so I will. 
 
So I’ve solved the (trivial) problem of moving one disk, when there is only one disk—just move it. 

Recursion always has a base case—a stopping condition to prevent it from going on forever. We’ve just found it. This “move one disk” case will actually be very important at a key time (always at the same time) in the more general solution. 

The thing that took me days to figure out was how the Tower problem is recursive. If the tower is n high, wouldn’t you say that moving all but one disk, then moving one disk, would solve it? Surely, right? Yes! This is the key insight into the problem—the “aha!” moment I did  not want stolen earlier.
So, generally, get the top (n-1) disks out of the way, deal with the biggest (bottom) disk with the trivial move, and that should be enough, right?

Think about this. The whole pile starts on A and wants to end up on B. So… can we move the top (n-1) from A to B, and then move the big disk from A to B? No! We’d break the rules, because this would mean that the biggest disk would end up on top of the smallest. So we can’t just go A->B in one fell swoop. We need to use C as a go-between.

What we must do is in fact this (to move everyone from A to B):
  • Almost the whole pile from A to C. 
  • The big disk directly from A to B in one move
  • The whole pile on C back to B. 
But notice how this is recursive. Steps 1 and 3 involve moving whole (slightly-smaller) piles to or from an intermediary rod, which isn’t the initial source or final destination.

We use C as a go-between to get most of the pile out of the way, to allow direct movement from A to B of the biggest disk. But to move most of the pile from A to C, B (the real destination) must have actually been a go-between, for an (n-1)-1 pile, move the biggest disk of the reduced pile, move the (n-1)-1 pile again, etc. 

Then, by the same logic, once we’ve gotten to the go-between Pole C, we need to get from there to the real destination Pole B—using the original origin Pole A as the go-between this time. 

Every one of these “move the big sub-pile” sub-problems is self-similar to the original problem, until the problem becomes simple enough that the answer is “just move one disk.”

A helpful observation is that you only ever have 3 possible moves: one is correct, one is useless, and one is illegal. 

The correct one gets us closer to accomplishing at least a sub-goal (or sub-sub, or sub-sub-sub-sub… depending on how many disks there are). The useless one undoes what we just did; we just wasted a move for no reason, so it will never be in our best interest to take this route if we want to minimize the number of moves. The last option isn’t really even an option. It’s illegal because it puts a bigger disk on top of a smaller one.   
Let’s go frame by frame to solve it for 3. Then, seeing this approach, you should in theory, be able to play with any number of disks. 

We start all on A, trying to go to C:
 









Move 1: Disk 1 from A to C

 


 Move 2: Disk 2 to B










Move 3: Disk 1 to B

 










Move 4: Disk 3 to C


 









Move 5: Disk 1 to A












Move 6: Disk 2 to C



 






Move 7: Disk 1 to C


 









The game on this website actually tells us we were as efficient as we could have hoped to be, with our 7 perfect moves (the modal might make it difficult to see, but, as intended, the whole pile of 3 disks is now on Peg C).

Even the 1-disk case has this 3-part structure, but it’s a little sneakier:
  • Move *no* disks from A to B. 
  • Move 1 disk from A to C. 
  • Move *no* disks from B to C. 
0, then, really is the most trivial of base-cases.

In any case, notice when (and how often) the biggest disk moved. In a 2-disk case, it moves on move 2 of 3; in this case, it moved on move 4 of 7; in a 4-disk case, it would move on move 8 of 15; in a 5-disk case, it would move on move 16 of 31. 

We’ve done the hardest part, which is discovering that “move the top n-1 to a go-between; move the biggest disk directly; move the top n-1 to the final destination” is an efficient process. The next part is figuring out the rules for determining when the big disk moves, and how many moves there are, in a general, efficient solution. 
Do you see a pattern, first in the move numbers of the big disks? (Other than that they only move once—which is key!). 
  • The big-disk move number is always even, or 1 in the 1-disk case
  • The big-disk move number doubles with each additional disk
  • The big disk move number is as close as possible (4/7, 8/15, 16/31, 32/63, etc.—getting even closer with more disks) to ½ of the total number of moves
  • If n-1 disks can move in X total, the big disk in the n-disk case, will move on move X+1; outside the trivial 1-disk case, X is always odd, and so X+1 is always even
  • If you’ve made it this far, you like computers, so you probably know about powers of two and binary. If you do, you probably recognize the sequence 1, 2, 4, 8, 16, 32, 64, etc.—as more than just “when the big disk moves.” These are the powers of two: start with 1, and for each additional term, double it to get the next one. Related to this sequence is 1, 3, 7, 15, 31, 63, etc.
Notice that both of these sequences play important roles in counting things. The pure power sequence counts when the big disk moves, when there are (power+1) disks: 2^0 is 1, so (power+1) = 1, so when there is 1 disk, the big disk moves on move 1; the big disk moves on move 2 when there are 2 disks (2 is 2^1 and 1+1 is 2 is the number of disks); on move 4 when there are 3 disks (4 is 2^2 and 2+1 is 3 is the number of disks), etc.

And the total number of moves is 2^(disks)-1: when there’s 1 disk, 2^1-1 is 1; when there are 2 disks, 2^2-1 is 3; when there are 3 disks (as we played out), 2^3-1 is 7; etc. For N disks, there’s exactly a one-to-one correspondence between efficient moves in the N-disk case and N-bit binary numbers.


  

Tuesday, May 27, 2025

Comments

Now is a good time to look at an essential part of coding that gives no instructions at all. In fact, the JVM explicitly does nothing with these lines. Just as you might see notes in the margin of a novel or textbook (explaining why something is important, connecting something you just read to something else, reminding yourself to come back later, etc.), you can, and should, do the same in code.


Java calls these little notes to yourself, without any bearing on the execution of the program, “comments.” The process of writing them is called “commenting” or “documenting” and, even early on, is just as essential to getting code done right as solving the problem correctly or solving the correct problem.

Every language has some delimiter that it looks for in lines of code, where, if it finds it, it ignores whatever is inside as a comment, not an instruction. In some languages, it’s ; or ;; (but recall that in Java, ; ends a statement which is code); in others, it’s # or ##. Java has two comment styles, based on different lengths: for short comments that take up just one line, and for longer multi-line comments. You can, if you wish, write a multi-line comment in that style, or with a bunch of single-liners.

Java’s single-line comments are indicated by //-- two forward slashes. Anything else in the line after two forward slashes will be ignored and treated as a comment, not an instruction. Multiline comments start with /* and end with */. Anything inside there is considered the comment.  

As you progress along your software development journey, you’ll become more and more aware of what needs comments and how detailed they should be. Just remember—you’re leaving notes to your future self (or someone else you may not know) about what the code does so that they can keep using, changing, or fixing it in the future.

Here, for example, is a well-written, functional Calculator class, missing any comments:

public class Calculator {

    public int add(int a, int b) {

        return a + b;

    }

    public int subtract(int a, int b) {

        return a - b;

    }

}


And now here is the same class, but with descriptive comments:

public class Calculator {

    /* This method adds two integers */

    public int add(int a, int b) {

        // Return the sum of a and b

        return a + b;

    }

 

    // This method subtracts the second integer from the first

    public int subtract(int a, int b) {

        return a - b; // Return the difference

    }

}

These examples are very simple, but even from something at this level, you can see how comments make it easier to know what to expect before reading a method in depth.

Generally, IDEs—at least IntelliJ—color-code things based on what they are. Reserved words like “public” or “static” or “int” are one color; method names like toString() are another color; properties, like “breed” in Dog.breed are the same color as method names; String literals, like “I like pizza” are a different color, etc. This helps someone just skimming the code know what things are, even at skimming speed. Most text, as in instructions, is just plain black. But comments are different. Most of the time, they appear as gray, but if you start your comments with certain words like “TODO” (not necessarily all uppercase) or “fixme” (not necessarily all lowercase), then the IDE will help you by color-coding those comments differently than regular ones, or anything else (usually in bright blue), to help draw your attention to the fact that you haven’t finished something, or that you’ve found a bug somewhere and need to fix it.

It takes time to learn how to write good comments, so don’t worry if you think you’re writing comments that are obvious—like, above a line “int x = 0;” writing “// this is an integer called x and its value is 0”—in the beginning. Those comments are an important stepping stone toward really good ones. As an exercise, go back through all the code you’ve written so far and write some comments.

And remember that comments are changes, so re-run your programs to test them and make sure they still work, since you might have accidentally commented out something you didn’t intend to!

Sunday, May 25, 2025

The for-each loop and indexing basics

 Congratulations, you’ve made it this far! You’re about to take a huge step in your Java journey and meet the first of many “data structures”. A data structure, simply put, is any way to store or organize data more complex than just one variable—more than just one String, or float, or double, or int, or short, or Boolean. 

This first data structure is called an array. Arrays are simply collections of fixed sizes of like things. You might have an array of ints that has space for 10, or an array of bytes with space for a million, or an array of FluffyUnicorns with space for 57. 

I don’t know—nor do I really care—what your array contains. All I know is this:

  • You gave it a name when you created it
  • You defined the maximum number of things it can hold when you created it
  • You defined the type of thing it can hold, and it will only ever hold that type of thing inside it
  • All your integers or booleans or FluffyUnicorns or whatever, exist in one contiguous block of memory—all together, one right after the other. 

The declaration of an array looks like this

type[] name = new type[size]; 

More concretely, 

int[] nums = new int[100];

This line means “Give me an array of integers, called nums, which has space for 100 integers. Initially, they should all be 0.”

Similarly,

FluffyUnicorn[] fluffies = new FluffyUnicorn[20]; 

defines an array of FluffyUnicorn objects—whatever that means—and names it fluffies, and declares that fluffies can hold at most 20 unicorns.

This is precisely the construction you’ve been seeing and taking for granted in the signature of the main method:

public static void (this we’ll ignore for now) main (String[] args) : means main gets passed in an array of Strings named args!

Arrays may be empty, that is, they may have nothing inside them. If that’s the case, then they’ll have their default value populated in each cell: the representation of 0 (or 0.0) for the numeric types, false for booleans, and null for objects like FluffyUnicorns. I’ve been deliberately vague about objects and nulls for now, but we’ll get to them in a few articles. 

Arrays give addresses to each object inside of them. Unlike humans, who start counting at 1, arrays (and a lot of things in computer science—not just Java!) start counting at 0. Be careful! A lot of beginner programmers encounter off-by-one errors when they forget that we start at 0, not at 1. This also means that the biggest address (called an “index”) available is the length of the array minus 1. For an array of length 7, the biggest index is 6, and so on.

Let’s imagine I have an array of  Strings like so: String[] friends = new String[3];

There are two ways to populate my 3 friends. 

I can either say:

friends = {“Bob”, “Alice”, “Charlie”};


or I can say:

friends[0] = “Bob”;

friends[1] = “Alice”;

friends[2] = “Charlie”;

These two ways of declaring things are exactly equivalent. In either case, friends[0] now has Bob, friends[1] now has Alice, and friends[3] now has Charlie. 

Let’s now say there exists an array of integers called nums, with initial state {0, 2, 4, 5, 7, 9, 11, 12}. 

How would I go about changing, let’s say, the element at index 2 to the value 3, and the element at index 5 to the value 8? By exactly the same procedure as we set friends[0] and so on. 

The changes would happen with:

nums[2] = 3; 

nums[5] = 8;

That means “go into nums at position 2 and put into that box the value 3” and “go into nums at position 5 and put into that box the value 8.” Whatever else may have been in nums[2] or nums[5] will be overwritten. 

Let’s say now I want to update each number by the same rule. \

I could use the for-loop we know (or any while-loop) and say something like:

for(int i = 0; i < nums.length; i++){

         nums[i] = nums[i] + 1;

}


and this would successfully increase every value by 1. 

But in Java, for operating on collections of things, like arrays, by the same rule every time, we have a special kind of for-loop called the for-each loop. 

The for-loop above and the for-each loop you’re about to see are exactly equivalent:


for(int num: nums){

       num++;

}


Reading this out in English, you should say “for each int num in nums, …”


Notice here that since we’re doing the same thing to each element of nums, we don’t care about the index. For-each loops are especially designed to operate on groups of things, like arrays and other structures we’ll learn about in future articles. 


Indexed-for loops

In this article, we’ll cover the indexed-for loop. Its syntax may seem a little more daunting than the do-while or while loops, but it’s always possible to convert a while to a for or vice versa.

For-loops look like this:

for(start state; end condition; variable change rule){
       do this stuff in here;
}

In real Java, you might see something like:

int res = 0;
for(int i = 0; i <= 10; i++){
       res+=i;
}
System.out.println(res);

How does this loop flow?
  • Before we enter the loop, we create res and give it the value 0
  • Now we are concerned about the loop. Inside it, there’s a counter called i, which is initially 0.
  • We’ll check against the condition i<=10
  • If that check succeeds, we’ll do whatever is in the braces
  • Then we’ll change i according to i++
  • Then we’ll see if we should keep going according to i<=10
  • Then if we should, we’ll do whatever is in the braces, and so on, and if not we exit
  • Once we exit the loop, we print res

Take a second to go back to the while-loop article and compare the while-loop implementation of the sum of the first 10 numbers to the for-loop implementation. Notice anything similar? Different?

The Do-while loop

 Next on our list of loops is the do-while loop.

The do-while loop is very similar to the while-loop, with one key difference.

The regular while-loop looks like:

while(something is true){
do this;
}

but the do-while loop looks like:

do {
this;
} while (something is true);

There is a key difference here. The while loop we saw is never guaranteed to execute. The while-loop checks the condition, and if it’s false from the get-go, nothing happens, and we just move on. But the do-while loop, on the other hand, encounters the “do this thing” part of its instructions before it encounters the condition to check. Do-while loops are therefore guaranteed to execute at least once, since they execute, then check, then execute, then check… and so on. Take a moment to realize that {execute, check, execute, check…} could produce different results than {check, execute, check, execute…}

The While-Loop

It’s very natural in computer science to want to do something over and over again. It’s for this reason that loops exist. Java has 4 loops, and we’ll cover one each in the next 4 articles: the while, the do-while, the indexed-for, and the for-each.


Syntactically, the while is the simplest. Logically, it’s the easiest to understand. We’ll start with it.

In plain English, “check if something is true, then do X; check if something is true, then do X; check if something is true, then do X.”

While loops look like this:

while(condition){
statements;
}

This should look very familiar, since if-statements are nearly identical. In fact, one of the ways you might hear about a while loop in a formal computer science course  is as  a repeated if-statement. You check the condition, then you do the thing, then you check the condition again, and you do the thing…

The same rules about braces apply to while loops. But, again, my recommendation is to always use them.

Imagine we want to sum all the numbers from 0 to (and including) 10.

We could set up a WhileLoopPractice class, and inside main say something like

int res = 0;
res+=1;
res+=2;
res+=3;

And so on…
and eventually come up with the (correct) answer 55.

But one of the benefits of a while loop is that we don’t need to be that verbose.

in that example, we could say:

int i = 0;
int res = 0;
while (i <= 10){
     res+=i;
}

If we then print out res after the loop finishes, it will have the right result.

Let’s look more closely at the bare-bones structure of a while-loop.

There is an iterator, a counter of some sort—in the sum example, we called it i. There is a condition being checked—in this case, whether i is 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, or 10. And there is at least one statement to be executed as long as the condition evaluates to true. Any valid Boolean expression can be that condition, just as was the case for if-statements.

Right out of the gate, there are two common bugs you might face:

  • What if a loop you intended to run never executes?
  • What if a loop you intended to run for a while and then have stop never stops?

It is easy to produce examples of both.

Scenario 1 looks like this:

int i = 0;

while(i < 0 && i > -10){
      System.out.println(“I’m negative!”);
     i--;
}

Notice here that i is set to 0. The condition specifies that i must be strictly less than 0, and strictly greater than -10.

But i is 0, which is not strictly less than 0. And given how we know AND works—both things fed to && must be true for && to return true—the very first check will fail and we’ll just skip over the loop. That loop, which we intended to print “I’m negative!” 10 times never actually executes.

Scenario 2 looks like this:

int i = 0;

while(i < 100){
     System.out.println(“I’m happy!”);
}

Look at what’s happening here: i is set to 0.
Is i’s value less than 100? “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”
Is i’s value less than 100? Yes “I’m happy!”

on and on forever

We’re never actually changing the value of i, so since it was initially less than 100, it will always be less than 100. This loop, in contrast to the other one we just saw that never started, never ends.

Writing this kind of loop by mistake is totally normal—and, for every programmer, doing this for the first time is a rite of passage. Thankfully, it’s a really easy mistake to fix; don’t sweat it!

All that needs to be done is, inside the loop, update i’s value.

Now, the fixed loop looks like:

int i = 0;

while(i < 100){
     System.out.println(“I’m happy!”);
     i++;
}

now, there’ll be an iteration where we print “I’m happy!” when i is 0, 1, 2, 3, 4, 5, …, 97, 98, and 99, and then, crucially, when I gets incremented to 100, it will no longer be true that 100 < 100, so the loop will stop. We’ll print “I’m happy!” exactly 100 times and then stop.


Conditionals

 Having learned the logical AND, OR, and NOT in a previous article, let’s move on to something only slightly more complex:

Set up a class (and your main method) called ConditionalPractice.

In the main method, declare a Boolean called isMonday. Set it to true, like this:

boolean isMonday = true;

In plain English, what we want to do is System.out.println() something if today is Monday.

What you’re about to see is something called pseudocode for the first time; it isn’t Java, so it won’t run if you try to run it in a class, but it gives a high-level, close-to-plain-English view of what code does in a way that what language choose to write in doesn’t matter. As we get more advanced, you’ll start seeing pseudocode in these articles a lot more frequently.

If today is Monday
               Print “Today is Monday”

In Java, we have if-statements.

Their syntax is the following

if(a condition){
     what to do if that condition is true;
}

That condition must always be evaluable to a boolean. Refer back to our Math lessons—“is 50 % 2 equal to 0?” (written as 50 % 2 == 0) is an example of such an expression that isn’t immediately a boolean like isMonday, but can become one. Both true-false questions and immediate booleans can go in the condition spot in the if.

You are not always obligated to have curly braces around ifs, but I always use them as a matter of personal preference. If your list of things to do inside a block only contains one thing, you can omit the braces. But if your list of thgs to do includes more than one statement, you must have them.

if(todayIsMonday)
     System.out.println(“Oh no!”);

works just as well (even though I don’t like it as much) as

if(todayIsMonday){
     System.out.println(“Oh no!”);
}

BUT:

(A)

if(todayIsMonday)
     System.out.println(“Oh no!”);
     System.out.println(“I don’t like Mondays”);

will produce a different—perhaps unintended result than

(B)

if(todayIsMonday){
     System.out.println(“Oh no!”);
     System.out.println(“I don’t like Mondays”);
}

In Java, if you omit braces from if-statements, Java assumes you only want the first statement inside the if bound by its condition.

So A and B are different. Can you figure out why? Pause for a second to think, and then keep reading.

A prints “Oh no!” only if today is in fact Monday, but prints “I don’t like Mondays” regardless.

B prints BOTH “Oh no!” and “I don’t like Mondays” only if today is in fact Monday, and nothing if it isn’t.

It's perfectly natural to want to do one of two other things:

·       If today is any other day of the week but Monday, print something else

·       If today is Tuesday, print X; if today is Wednesday, print Y; if today is Thursday, print Z; and so on

For this, we need an extra bit of syntax: the else clause.

Else clauses are always paired with if-statements, and always come after them. Else clauses have the same brace rules as if-statements, but, again, I like to always use them even when not technically required by the compiler.

To write the first bullet point, we need a straight-up else statement without any frills.

in pseudocode, it looks like:

If a number is even:
     Print “YAY!”
Else
     Print “BOO!”

In Java, that would look like


if(){
} else{
}

and the code to be executed in each situation would go inside the relevant curly braces.

So back to our other-days example.

·       If today is Monday, print “I don’t like Mondays!”

·       Otherwise, for any other day of the week, print “Today is OK, I guess”

if(todayIsMonday){
      System.out.println(“I don’t like Mondays!”);
} else{
     System.out.println(“Today is OK, I guess”);

Now, what if you wanted to get a little more specific with how you handled branching, and have a few more cases? Perhaps “I really enjoy the weekend!!” gets printed if today is Saturday or Sunday?

in that case, let’s assume that all these variables are properly created and initialized (I won’t show that here, but you’ll need to make sure that’s true if you’re following along in your IDE window):

First, in pseudocode:

If today is Monday
               Print “I don’t like Mondays!”
If we know today isn’t Monday, but we still check if today is the weekend and find out it is
               Print “I really enjoy the weekend!!”
In every other situation, when today isn’t Monday or the weekend
               Print “Today is OK, I guess”

now in Java (remember to declare and initialize everything properly or you’ll get an error!)

if(todayIsMonday){
      System.out.println(“I don’t like Mondays!”);
}else if (todayIsWeekend){
     System.out.println(“I really enjoy the weekend!!”);
}else{
      System.out.println(“Today is OK, I guess”);
}

You could, in theory, have as many different else-if cases in between as you wanted. Make that an exercise. Go through and set one day a week to be true at a time, and run the code many times, setting a different message to be true for each day of the week

Remember, it would look something like:

if(todayIsMonday){
      System.out.println(“I don’t like Mondays!”);
} else if (todayIsTuesday){
     System.out.println(“I go to the store on Tuesdays”);
} else if (todayIsWednesday){
     System.out.println(“I go to the park on Wednesdays”);
}  else if (todayIsWeekend){
     System.out.println(“I really enjoy the weekend!!”);
}else{
      System.out.println(“Today is OK, I guess”);
}

of course, you could do something purely mathematical—

Like:

·       Start with 5040

  • Using % to check, if x (5040) is a multiple of 2, print “2!!”
  •  Else if x is a multiple of 3, print “3!!”
  • Else if x is a multiple of 4, print “4!!”
  • Else if x is a multiple of 5, print “5!!”
  • Else if x is a multiple of 6, print “6!!”
  • Else if x is a multiple of 7, print “7!!”
  • Else if x is a multiple of 8, print “8!!”
  • Else if x is a multiple of 9, print “9!!”
  • Else if x is a multiple of 10, print “10!!”
  • Else—that is, only as a last resort, if nothing else triggers—print “prime??”

Be careful—I’ve set a trap here! This is like the days of the week example—so only the first true thing triggers (or the else), not everything that is true!

To make everything that is true trigger, turn all else-ifs into regular ifs. (Ifs can stand alone without an else, but the reverse is not true.) 

Logical Operations

 In computer science, it’s very important when writing code to be able to express a few key ideas. In Java, :

  • A AND B (only true if both are true) is &&
  • A OR B (only false if both are false) is ||
  • NOT A (the opposite; true becomes false, false becomes true) is !

 

They all look reasonably similar to what you’d expect, but just like = is not to check for equality (that’s ==, since = is for assigning, and the single = can’t mean both), they are slightly different from what you might have first thought, for a very similar reason. (We’ll cover what single-& and single | mean in Java much later.)

In Java, you can AND booleans or Boolean expressions with &&, as in A && B; you can OR them with ||, as in X || Y; and you can NOT them by !, as in !isEmpty().

You can, of course, connect them into much longer chains: A AND (B OR C) AND NOT D OR (E AND F) would look like

A && (B || C) && !D || (E && F), and so on. Depending on the values of A..F, this will change the value of the whole expression.

Notice in this example the use of parentheses to force ordering, just as in math.

In exactly the same sense as we have PEMDAS/BODMAS in math, logical operations also have an order of precedence: parentheses go first, then ==, then ! (NOT), then && (AND), then || (OR).  

Saturday, May 24, 2025

Basic Math

 People associate computers with math, and that association is certainly warranted. A lot of code involves explicitly doing math, and behind the scenes, there’s even more math going on that you don’t see, but which is very important.


Let’s start learning how to do math in Java.

Create a new class. This time, let’s call it MathLesson.

public class MathLesson{

}

As before, give yourself a main method:

public class MathLesson{

public static void main (String[] args){

}

}

Now, we briefly need to take a detour from Math to look at data types.

Every variable in Java has 3 things: a name, a type, and a value. Let’s start in plain English: “every box has a name, a thing that it can hold, and the information in that thing.”

In Java, there are two types of variables: primitive types, and object types. You can easily distinguish primitives from objects by the capitalization (or not) of the names of the types.

There are exactly 8 primitive types in Java:

  • int: for whole numbers between (and including) -2,147,483,648 to 2,147,483,647
  • double: for decimal numbers between (and including) -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
  • float: for numbers with decimals between (and including) -2,147,483,648 to 2,147,483,647
  • long: for whole numbers between (and including) -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
  • short: for whole numbers from -65536 to 65535
  • byte: for whole numbers from -128 to 127
  • char: for single characters, like a, {, <, and so on
  • boolean: only true or false (represented like this: always all lowercase)

Anything else is not a primitive, which makes it an object. Long sequences of characters (called a String—note the capitalization of String, so it isn’t primitive) are one of these (as in public static void main(String[] args). String here is an object type.

For the simple math we want to deal with here, we only need the 8 primitive types.

In Java, we operate on the following principle: “Put what is on the right into the ‘box’ on the left.” In grade school, you might have seen 2x + 3 = y, or y = 2x + 3. Mathematically, those are equivalent, but the “gets” operation (called “assignment”, denoted with the equals sign—we check for equality by another way moves right to left.

Some basic operations (always with assignment) we might want to do are:

·       add two numbers and assign the result into a third

·       add a and b, and put the result back in a

·       multiply two numbers

·       divide two numbers

·       subtract two numbers

·       find the remainder after one number is divided by another

·       check if two numbers are equal

Addition is represented with +, subtraction with -, multiplication with *, division with /, and the modulus/remainder operator with %.

3  + 1  is 4
3 * 2  is 6
10 % 3 is 1 (this means that 10 divided by 3 leaves 1 left over)
3 – 2 is 1
3 / 1 is 3


 

Inside main, let’s declare 2 variables, x and y, and give them the values 10 and 5.

public class MathLesson{

public static void main (String[] args){

int x = 10;

int y = 5;

}

}

Now, using the System.out.println() we learned in the last lesson, let’s just print them out. You can pass the names of variables into println, and it will display their values.

public class MathLesson{

public static void main (String[] args){

int x = 10;

int y = 5;
System.out.println(x);
System.out.println(y);

}

}


This, line by line: creates x, lets it store integers, and puts 10 in that box; creates y, lets it store integers, and puts 5 in that box; then displays x (10) to the screen and displays y (5).




 

Now, let’s create a few more variables, all integers: res1, res2, res3, res4, res5.

public class MathLesson{

public static void main (String[] args){

int x = 10;

int y = 5;
System.out.println(x);
System.out.println(y);

int res1;

int res2;

int res3;

int res4;

int res5;

}

}


 

Now, let’s do each operation with 10 and 5, and save each one into a different variable.
 
public class MathLesson{

public static void main (String[] args){

int x = 10;

int y = 5;
System.out.println(x);
System.out.println(y);

int res1;
res1 = x + y;

int res2;
res2 = x * y;

int res3;
res3 = x – y;

int res4;
res 4  = x / y;

int res5;
res5 = x % y;

}

}


Print them, just as we printed x and y, with System.out.println().

res1 will be 15, res2 will be 50, res3 will be 5, res4 will be 2, and res5 will be 0.

Now, change all instances of int to something else (double, float, long, short, or byte) and try again.

It is common practice—and your code looks better—if you combine declarations and assignments into one line. You can, of course, say

int res1;
res1 = x + y;

But it saves a few keystrokes and is much more common to simply say

int res1 = x + y;

The other approach, though, with split declarations and assignments, is important. It reveals something key about variables. Once you declare them with a type, you don’t reference them by their type again. (And since this is Java, that type won’t change.) In this example, res1 will always be an integer. At the moment it was created, it got the value 0 by default, but one line later, it was assigned the value of the expression x plus y, which in this case is 10 + 5 is equal to 15.

It is perfectly reasonable, let’s say, to want to check if the result of x times y (which we stored in res2, as above) is 100 or not. (Spoiler alert, it’s 5 times 10, which is obviously 50, which is not 100).

For this, we need two things:

1.       A variable that can store the answer to yes/no, true/false, on/off questions—these are booleans

2.       A way to represent “checking if something is equal” given we’ve already used one single = sign to mean “gets the value of.” The expression x = y does not mean “x is equal to y” as in mathematics. It means “give the variable x the value of the variable y.”

We can say, inside our main method something like the following (after res2 has been declared – so it exists—and initialized, so it has a non-zero/non-default value):

boolean answer = res2 == 100;


This means, in plain English, the following.

  • Make me a box somewhere in memory that stores yes/no, true/false, on/off values
  • Call that box “answer”
  • Give it the value of “does res2 equal 100?” (false)

For clarity, you could see this:

boolean answer = (res2 == 100);

This just makes things easier on the eyes. Parentheses work just as they do in math. That is, do the check first, then assign the value (true or false—false here) into answer.

There is one more trivial operation I want to cover now. Going through high school and my undergrad, I’ve probably used this 10,000 times already, and someone with decades of experience could easily have written this 100,000 times or more: a shortcut to add 1.

Of course, let’s say you have an integer called addToMe and it current has a value 6. That looks like

int addToMe = 6;

If you wanted to add one to addToMe, you could certainly say that

addToMe = addToMe + 1;

(Remember, this is not algebra class; we are not saying that 6 equals 7; we are saying that we’re going to look in the box called addToMe, find its value, increase it by 1, and put the new value back in that same box.)

This is perfectly valid, but there’s a much more common and elegant way to do this in Java: by using another operator, which is ++

++ only works to add 1. (Any other number being added besides 1 must still be in the form x = x + y;)

I can say

int addToMe = 6;

and then I can print it, and get 6.

then I can say

addToMe++;

And print it—and now the value will be 7.



If I say again, another line later,

addToMe++;

now, we are “plus-plus-ing” 7, so printing addToMe a third time would show 8. This is more properly called “incrementing.”

Just as there is ++ to add one, there is -- to subtract one.

Now, imagine we have

int subFromMe = 10;

if we print it, we get back 10.

if we now say

subFromMe--;

and print again, we’ll see 9

This should give you a really solid base for how to do simple math in Java. There’s more complex math coming later, don’t worry! But for now, look at this code, try to change some values, see what happens, and understand why! The best way to get better at Java is to practice!

Hello World

 Hello World!


My name is Andre da Silva, and I just graduated from the University of Illinois Springfieldsumma cum laude with a bachelor’s in computer science! I’ve learned so much throughout my degree, and I know there’s still so much more to learn in the years to come.

I’ve always felt both that teaching is the best way to learn and that I wanted to give back to computer science education. (Richard Feynman pioneered this, and I took the technique from him.) So many people have helped me get to the point where I am today, so it only feels right, now, having graduated, to find a forum to go back to the basics and help people who are just getting started. My parents and instructors have been so supportive of my journey through computer science, it only feels fitting to go back to the basics to help the next generation who are just now getting started.

The most natural place to start is where every Java programmer ever has started: with the classic “Hello World” program.

This is a program that can be written in any language—I think it was first written in the ever-important C, about 50 years ago, sometime in the early 1970s.

You need what’s called an “integrated development environment” to write and test good Java code. You can technically write Java without one, but that’s like using Notepad for everything when Google Docs or Microsoft Word are available. Some people do literally write their code in Notepad—and sometimes you have to, during interviews, for example—but unless absolutely required, I always recommend using an IDE because it’s so helpful.

My favorite is IntelliJ IDEA. For years, while I was in school, I got access to the Ultimate version. If you’re not in school—yet or anymore—or you don’t have a job at a company willing to give you the Ultimate version, the Community Edition is just fine!

To get the Community Edition, go to Download IntelliJ IDEA, and select the right download for your operating system. I run Windows 10 on my laptop, so I selected Windows. The setup wizard will prompt you for a few things. You can leave them as defaults. IntelliJ is a big program, so even the Community Edition takes a few minutes to download.

While you wait, feel free to read ahead.

Today, working on Hello World, our goal is to simply write a program that puts “Hello World” on the screen. This is such a simple operation that programmers in almost any language will write their language’s version of this program just to make sure everything is installed and configured correctly. For novice programmers, writing your first Hello World is a real rite of passage.

Before we look at any code, let me caution you that Java has a reputation—at least at this level—for being quite verbose. In Python (another language), all you need to do is write the following in a Python file:

print(“Hello, World”)

Java’s Hello World includes a lot more extra code just to get things running. We call that “boilerplate.” Some people—particularly those Python developers (look how clean print(“Hello world!”) is!)—poke fun at Java and its developers for how wordy the language is. But after you write in it enough, you’ll get used to it, and you’ll come to understand what it all means.

For now, it’s OK to take everything for granted. Explanations will come in later posts, and you’ll surely learn what everything means.

Once you have IntelliJ launched, click “File,” then “New,” then “Project”. For now, you can then select “Java” and leave everything as defaults. Give the project a name like “MyFirstProject.” Avoid spaces. You need to define a JDK—the Java Development Kit—to be able to write and run Java. Use version 23 (as of May 2025 when this was written).

Java is written in .java files. Some magic happens—more on that later—that translates Java statements like “int x = 5;” (which in human language means “give me a box somewhere in memory, let it store integers from about minus-2-billion to plus-2-billion, give that box the name x, and put inside that box the value 5”) into instructions the computer can understand. The actual code being run by the computer exists in files that end in .class; these aren’t meant to be read or written by humans. We use the .java files, and the .class files are just for the computer. The process for converting .java to .class is “machine-independent” thanks to the Java Virtual Machine—so you can run Java code on any computer, with any operating system, as long as you have access to the JVM.

Once your project is all set up, again, click File, New, but this time select “Java Class.” This will prompt you to name your file. For now, remember this: The name of the file and the name of the class must always match. The PepperoniPizza class is defined in PepperoniPizza.java, the FluffyPuppy class is defined in FluffyPuppy.java, and so on. “Fluffy Puppy” or “Pepperoni Pizza” are not allowed (because of the spaces), and neither is “1FluffyPuppy” (because Java classes can’t start with a number).

We are going to write the HelloWorld class, so that needs to go into a file named HelloWorld.java. The HelloWorld file contains the HelloWorld class.

IntelliJ is nice because when you name the HelloWorld class, it will automatically give you the following:

public class HelloWorld{

}

Everything we write to get “Hello World” on the screen will go inside those two initial curly braces. Notice that for each opening one, there’s a closing one, and vice versa. Braces—and eventually other paired symbols, like square brackets [] and parentheses ()—must always be paired, or an error will occur.

Inside those curly braces, write the following, and then pause again:

public static void main (String[] args){}

several parts of this must be exact: public, followed by static, followed by void, followed by main, followed by an open parenthesis, followed by String[] followed by a name (by convention, args, but it could be anything), followed by a closing parenthesis.

Note the capitalization: everything except String is deliberately lowercase, and String is deliberately uppercase. More on why in an upcoming article.

Your file should now have the following:

public class HelloWorld {

    public static void main (String[] args) {

    }

}

Later on, you can get away with just typing the letters psvm and then hitting Tab and IntelliJ will know what you want. But, for now, as you're just getting started, stick to the whole thing. Each part of this matters, and you will shortly learn why.

Now, inside main’s curly braces (so also inside HelloWorld’s), write the following—again, copying capitalization exactly:

System.out.println(“Hello World”);

Your code should then look like

public class HelloWorld {

    public static void main (String[] args){

System.out.println(“Hello World”); 

  }

}


And now, finally, you can run it!

You have 3 choices for how to do this:

  • Click the green play button next to the public class line (in “the gutter”)
  • Click the green play button next to the public static void main line (also in “the gutter”)
  • Click the green play button next to the green outline of a ladybug in the upper right corner.

IntelliJ will now convert your human-readable code into something machine-readable, and the result will be that a window will open underneath where your code is (we call that window “the console”), and in that window, you will see 2 things: “Hello World” on the screen, and something like “Process terminated: exit condition 0”. The “Hello World” on the screen is what we wanted, and “Process terminated: exit condition 0” means everything worked, and your program didn’t crash! (By convention, 0 is the status code for success, and any other number means some type of failure. Be careful typing exactly what I have above; if you make a typo, you won’t see “Hello World,” and instead of exit code 0, you’ll probably get exit code 130, indicating a fatal error).

If you’ve made it this far, take a deep breath, give yourself a pat on the back, and take a minute to celebrate this milestone: you just wrote your first Java program!

Switch

 Other than if/if-else/if-else if-else and the ternary operator, there is yet another common and important conditional expression in Java th...