Beginner

Control Flow in V (Vlang)

Learn conditionals, match expressions, and loops in V with practical Docker-ready examples covering if/else, match, the unified for loop, and loop control

Control flow determines the order in which your program’s statements run. It is how a program makes decisions and repeats work. V takes a deliberately minimal approach: where most languages scatter switch, while, and do-while across several keywords, V provides a single match construct and a single loop keyword — for. This fits V’s design philosophy of being a small language with no hidden control flow that you can learn in a weekend.

As a multi-paradigm language with imperative roots, V uses familiar structured control flow, but with its own opinions. There are no parentheses around conditions, braces are always required, match does not fall through, and there is deliberately no ternary operator. Instead, both if and match are expressions — they evaluate to a value — so a one-line conditional assignment reads naturally without ? :.

In this tutorial you will learn how to branch with if/else, match values and ranges with match, iterate with the versatile for loop, range over arrays, and control loops with break and continue — including V’s labeled loops.

Conditionals: if / else

V’s if statement needs no parentheses around the condition, but the braces are mandatory. Because if is an expression, it can also produce a value directly — V’s idiomatic replacement for the missing ternary operator.

Create a file named conditionals.v:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn main() {
	temperature := 72

	// Basic if / else if / else chain (no parentheses around the condition)
	if temperature > 80 {
		println('It is hot outside')
	} else if temperature > 60 {
		println('The weather is pleasant')
	} else {
		println('Bring a jacket')
	}

	// if is an expression: each branch yields a value
	score := 85
	grade := if score >= 90 {
		'A'
	} else if score >= 80 {
		'B'
	} else {
		'C or below'
	}
	println('Grade: ${grade}')
}

The expression form (grade := if ... { } else { }) keeps short conditional assignments compact while staying explicit. Since V has no null and requires every branch to return a value of the same type, the compiler guarantees grade is always initialized.

Match Expressions

V’s match replaces the C-style switch. It never falls through, so there is no break to forget, and a single branch can list several values separated by commas. Like if, match is an expression that can return a value, and it can match against inclusive integer ranges written with ....

Create a file named match_expr.v:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fn main() {
	day := 'Saturday'

	// match used as an expression; a branch can list several values
	kind := match day {
		'Saturday', 'Sunday' { 'weekend' }
		'Friday' { 'almost the weekend' }
		else { 'a workday' }
	}
	println('${day} is ${kind}')

	// match on inclusive integer ranges (0...11 means 0 through 11)
	hour := 14
	greeting := match hour {
		0...11 { 'Good morning' }
		12...17 { 'Good afternoon' }
		else { 'Good evening' }
	}
	println(greeting)

	// match used as a statement, running a block of code per branch
	n := 2
	match n {
		1 { println('One') }
		2 { println('Two') }
		else { println('Many') }
	}
}

Because V’s match requires an else branch (or exhaustive coverage of every possible value), you cannot accidentally leave a case unhandled. This is the same safety idea behind switch in Go, taken a step further by making the match an expression.

Loops: the for Statement

V has a single looping keyword: for. It covers every looping need through several forms — the classic three-component loop, a condition-only “while” form, and a bare infinite loop. Remember that V is immutable by default, so any variable a loop reassigns must be declared mut.

Create a file named loops.v:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn main() {
	// 1. Classic three-component loop: init; condition; increment
	for i := 1; i <= 5; i++ {
		println('Count: ${i}')
	}

	// 2. Condition-only form (V's equivalent of a while loop)
	mut n := 10
	for n > 1 {
		n = n / 2
		println('n is now ${n}')
	}

	// 3. Bare infinite loop, exited with break
	mut total := 0
	for {
		total += 10
		if total >= 30 {
			break
		}
	}
	println('Total reached ${total}')
}

There is no while keyword in V — for condition { } does the job. Likewise, for { } with no clauses is an infinite loop that you exit with break. One keyword, three behaviors.

Ranging Over Collections and Loop Control

The in clause iterates over ranges and arrays. A range written 0 .. 5 is half-open (it includes 0 but stops before 5). Combined with break and continue — and V’s labeled loops — this handles most real-world iteration.

Create a file named range_loops.v:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
fn main() {
	// Iterate over a half-open range: 0 .. 5 yields 0, 1, 2, 3, 4
	for i in 0 .. 5 {
		print('${i} ')
	}
	println('')

	// Iterate over an array, capturing both index and value
	fruits := ['apple', 'banana', 'cherry']
	for index, fruit in fruits {
		println('${index}: ${fruit}')
	}

	// continue skips to the next iteration
	for i in 1 .. 7 {
		if i % 2 != 0 {
			continue // skip odd numbers
		}
		println('Even: ${i}')
	}

	// A label lets break exit an outer loop, not just the inner one
	outer: for i in 1 .. 4 {
		for j in 1 .. 4 {
			if i * j > 4 {
				println('Stopping at ${i} x ${j}')
				break outer
			}
		}
	}
}

The for index, value in array form is the idiomatic way to walk an array in V. Labeled loops (outer:) work with both break and continue, giving you precise control over nested loops without resorting to flag variables.

Running with Docker

You can run every example without installing V locally by using the official thevlang/vlang:alpine image.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Pull the official V image
docker pull thevlang/vlang:alpine

# Run the conditionals example
docker run --rm -v $(pwd):/app -w /app thevlang/vlang:alpine v run conditionals.v

# Run the match example
docker run --rm -v $(pwd):/app -w /app thevlang/vlang:alpine v run match_expr.v

# Run the loops example
docker run --rm -v $(pwd):/app -w /app thevlang/vlang:alpine v run loops.v

# Run the range and loop-control example
docker run --rm -v $(pwd):/app -w /app thevlang/vlang:alpine v run range_loops.v

Expected Output

Running conditionals.v:

The weather is pleasant
Grade: B

Running match_expr.v:

Saturday is weekend
Good afternoon
Two

Running loops.v:

Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
n is now 5
n is now 2
n is now 1
Total reached 30

Running range_loops.v:

0 1 2 3 4 
0: apple
1: banana
2: cherry
Even: 2
Even: 4
Even: 6
Stopping at 2 x 3

Key Concepts

  • No parentheses, mandatory braces — Conditions are written without surrounding (), but { } is always required, even for a single statement.
  • if is an expressionx := if cond { a } else { b } assigns a value directly; this is V’s replacement for the ternary operator it deliberately omits.
  • match never falls through — Branches do not leak into the next, so there is no break to forget. A branch can list several comma-separated values.
  • match is exhaustive — You must cover every case or provide an else, so the compiler catches unhandled values.
  • One loop keywordfor covers C-style loops, while-style loops (for cond {}), infinite loops (for {}), and in-based iteration. There is no while or do-while.
  • Ranges and infor i in 0 .. 5 iterates a half-open range, while for i, v in arr yields each index and value.
  • Immutable by default — A variable a loop reassigns must be declared mut; otherwise the compiler rejects the program.
  • break and continue with labels — Labeled loops (outer:) let you break out of or continue an outer loop directly, avoiding flag variables.

Running Today

All examples can be run using Docker:

docker pull thevlang/vlang:alpine
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining