Definition
- Vowel – one of the five letters a, e, i, o, u (case-insensitive).
- Consonant – any letter that is not a vowel.
- Algorithm – lowercase the input, loop through each character, check
Character.isLetter(c)to skip non-letters, then test if the character is a vowel or a consonant and increment the appropriate counter. - Complexity – O(n) time for the single pass and O(1) space for the two counters.
- Generalisation – the same classify-and-tally pattern works for counting digits, whitespace, or special characters.
National Bank of India wants a quick readability metric on Rajesh's chosen account nickname. A clerk reads each letter of the nickname and makes a tally mark in the vowel column for a, e, i, o, u and in the consonant column for every other letter, skipping any numbers or spaces entirely.
Understanding the Concepts
What does it mean to count vowels and consonants?
In the English alphabet, vowels are the letters a, e, i, o, and u. Every other letter is a consonant. When counting vowels and consonants in a string, you examine each character, decide which category it belongs to, and increment the appropriate counter. Characters that are not letters, such as digits, spaces, and punctuation, are skipped entirely.
package com.bank.core;
public class VowelConsonantCount {
public static void main(String[] args) {
String nickname = "Rajesh Kumar 2024";
int vowels = 0, consonants = 0;
for (char c : nickname.toLowerCase().toCharArray()) {
if (Character.isLetter(c)) {
if ("aeiou".indexOf(c) >= 0) {
vowels++;
} else {
consonants++;
}
}
}
System.out.println("Vowels: " + vowels);
System.out.println("Consonants: " + consonants);
}
}
Vowels: 4
Consonants: 7
Why lowercase the string first?
Lowercasing the string before scanning means you only need to check five characters (a, e, i, o, u) instead of ten (both cases). This simplifies the vowel test and eliminates the risk of forgetting to handle uppercase vowels.
package com.bank.core;
public class LowercaseFirst {
public static void main(String[] args) {
String input = "PRIYA Sharma";
String lower = input.toLowerCase();
int vowels = 0;
for (char c : lower.toCharArray()) {
if ("aeiou".indexOf(c) >= 0) {
vowels++;
}
}
System.out.println("Vowels in '" + input + "': " + vowels);
}
}
Vowels in 'PRIYA Sharma': 4
How do you skip non-letter characters?
The Character.isLetter(c) method returns true only for alphabetic characters. Using it as a guard before the vowel check ensures that digits, spaces, punctuation, and other non-letter characters are ignored. Without this guard, a naive implementation that only checks for vowels and treats everything else as a consonant would wrongly count digits and spaces as consonants.
package com.bank.core;
public class IsLetterGuard {
public static void main(String[] args) {
String accountTag = "SAV-2001 Rajesh";
int vowels = 0, consonants = 0, skipped = 0;
for (char c : accountTag.toLowerCase().toCharArray()) {
if (!Character.isLetter(c)) {
skipped++;
} else if ("aeiou".indexOf(c) >= 0) {
vowels++;
} else {
consonants++;
}
}
System.out.println("Vowels: " + vowels);
System.out.println("Consonants: " + consonants);
System.out.println("Skipped: " + skipped);
}
}
Vowels: 3
Consonants: 7
Skipped: 5
How can you test if a character is a vowel efficiently?
There are several ways to test if a character is a vowel. The cleanest approaches are:
"aeiou".indexOf(c) >= 0: checks if the character exists in the vowel string.- A
Setcontaining the five vowels and callingcontains(c). - A
switchstatement or expression matching the five vowels.
package com.bank.core;
import java.util.Set;
public class VowelCheckMethods {
private static final Set<Character> VOWELS = Set.of('a', 'e', 'i', 'o', 'u');
public static void main(String[] args) {
char c = 'a';
// Method 1: indexOf
System.out.println("indexOf: " + ("aeiou".indexOf(c) >= 0));
// Method 2: Set
System.out.println("Set: " + VOWELS.contains(c));
// Method 3: switch expression (Java 14+)
boolean isVowel = switch (c) {
case 'a', 'e', 'i', 'o', 'u' -> true;
default -> false;
};
System.out.println("Switch: " + isVowel);
}
}
indexOf: true
Set: true
Switch: true
What are the edge cases?
Edge cases include empty strings, strings with no letters at all, strings that are all vowels, and strings that are all consonants.
package com.bank.core;
public class VowelEdgeCases {
public static int[] count(String s) {
int vowels = 0, consonants = 0;
for (char c : s.toLowerCase().toCharArray()) {
if (Character.isLetter(c)) {
if ("aeiou".indexOf(c) >= 0) vowels++;
else consonants++;
}
}
return new int[]{vowels, consonants};
}
public static void main(String[] args) {
print("Empty", count(""));
print("Digits only", count("12345"));
print("All vowels", count("aeiou"));
print("All consonants", count("bcdfg"));
print("Mixed", count("Rajesh"));
}
private static void print(String label, int[] result) {
System.out.println(label + " -> vowels=" + result[0] + ", consonants=" + result[1]);
}
}
Empty -> vowels=0, consonants=0
Digits only -> vowels=0, consonants=0
All vowels -> vowels=5, consonants=0
All consonants -> vowels=0, consonants=5
Mixed -> vowels=2, consonants=4
What about extending this to count more categories?
The classify-and-tally pattern generalises easily. You can extend it to count digits, whitespace, and special characters by adding more counters and more classification branches.
package com.bank.service;
public class CharacterAnalyzer {
public static void main(String[] args) {
String transactionNote = "EMI: Rs.12,500 for Loan #A2024";
int vowels = 0, consonants = 0, digits = 0, spaces = 0, special = 0;
for (char c : transactionNote.toLowerCase().toCharArray()) {
if (Character.isLetter(c)) {
if ("aeiou".indexOf(c) >= 0) vowels++;
else consonants++;
} else if (Character.isDigit(c)) {
digits++;
} else if (c == ' ') {
spaces++;
} else {
special++;
}
}
System.out.println("Vowels: " + vowels);
System.out.println("Consonants: " + consonants);
System.out.println("Digits: " + digits);
System.out.println("Spaces: " + spaces);
System.out.println("Special: " + special);
}
}
Vowels: 5
Consonants: 8
Digits: 7
Spaces: 4
Special: 5
What about using Java streams?
You can use Java streams for a more functional approach. The chars() method on String returns an IntStream that you can filter and count. This is more concise but slightly slower due to boxing overhead.
package com.bank.core;
public class VowelCountStream {
public static void main(String[] args) {
String name = "Pankaj Architect";
String lower = name.toLowerCase();
long vowels = lower.chars()
.filter(c -> Character.isLetter(c))
.filter(c -> "aeiou".indexOf(c) >= 0)
.count();
long consonants = lower.chars()
.filter(c -> Character.isLetter(c))
.filter(c -> "aeiou".indexOf(c) < 0)
.count();
System.out.println("Vowels: " + vowels);
System.out.println("Consonants: " + consonants);
}
}
Vowels: 5
Consonants: 9
What is the time and space complexity?
The algorithm makes a single pass through the string, visiting each character exactly once. The vowel test is O(1) because the set of vowels is fixed at five characters. The total time complexity is O(n) where n is the length of the string. The space complexity is O(1) because only a few integer counters are used, regardless of the input size.
If you call toLowerCase() first, that creates a new String of size n, adding O(n) space. To stay at O(1) space, you can compare each character against both cases inline instead of lowercasing the whole string.
What are common mistakes?
The most common mistake is not guarding with isLetter() and treating every non-vowel character as a consonant. This wrongly counts digits, spaces, and punctuation as consonants.
package com.bank.core;
public class VowelMistake {
public static void main(String[] args) {
String input = "SAV 2001";
// WRONG: no isLetter guard
int wrongConsonants = 0;
for (char c : input.toLowerCase().toCharArray()) {
if ("aeiou".indexOf(c) < 0) {
wrongConsonants++; // counts space and digits too
}
}
System.out.println("Wrong consonants: " + wrongConsonants);
// CORRECT: isLetter guard
int correctConsonants = 0;
for (char c : input.toLowerCase().toCharArray()) {
if (Character.isLetter(c) && "aeiou".indexOf(c) < 0) {
correctConsonants++;
}
}
System.out.println("Correct consonants: " + correctConsonants);
}
}
Wrong consonants: 6
Correct consonants: 2
Visual Overview
Character Classification Flow
═════════════════════════════
Input: "SAV 2001"
For each character c:
┌─────────────────────┐
│ Is c a letter? │
│ isLetter(c) │
└─────────┬───────────┘
yes │ no
▼ ▼
┌─────────────┐ ┌────────┐
│ Is vowel? │ │ SKIP │
│ aeiou has c │ │(digit, │
└──┬──────┬───┘ │space) │
yes│ │no └────────┘
▼ ▼
vowels++ consonants++
Key Takeaway
Counting vowels and consonants is a single linear pass with two counters. Lowercase the string first so you only have to test five vowel letters, then loop through each character. For each character, first check whether it is a letter at all, and if it is not a letter, skip it so digits, spaces, and punctuation do not pollute the counts. If it is a letter, test whether it is one of a, e, i, o, u, incrementing the vowel counter if so and the consonant counter otherwise.
In the banking project at National Bank of India, Rajesh used this in an analytics panel that shows how many vowels and consonants a customer's account nickname contains. The isLetter() guard is the critical detail, because nicknames frequently contain digits and spaces, and a naive implementation that only checks for vowels and treats everything else as a consonant would wrongly count those non-letters.
The pattern generalises easily to counting digits, whitespace, or any character category by adding more counters and more classification branches. For a functional approach, Java streams with chars() and filter() offer concise code at the cost of slightly more overhead.
Remember one sentence: lowercase first, guard with isLetter(), then classify each character as a vowel or consonant in a single O(n) pass.
---