Talentd
FleetCode
Make and follow sheets.Sign in

UTF-8 Validation solution and explanation

MediumArrayBit Manipulation6 min read46.7% acceptanceUpdated 9 Apr 2026
UTF-8 Validation LeetcodeVenkatesh Thallam · 16:27 · 4K viewsWatch on YouTube
9 more videos on this problem
  1. UTF-8 Validation LeetcodeVenkatesh Thallam · 16:27 · 4K views
  2. 393. UTF-8 Validation || Leetcode || HindiTech Adora by Nivedita · 19:12 · 2K views
  3. 393. UTF-8 Validation | English | MediumSimple Code - 단순코드 · 11:56 · 1K views
  4. 393. UTF-8 Validation "python" leetcodeSugar Coat · 9:01 · 1K views
  5. UTF-8 Validation | LeetCode 393 | Google Interview Question | Bit Manipulation @LearnOverflowLearn Overflow · 20:19 · 1K views
  6. Daily Leetcode #896: Sep 13, 2022 - 393. UTF-8 ValidationProgramming Live with Larry · 13:38 · 834 views
  7. LeetCode - 393. UTF-8 Validationciaociao_o · 12:39 · 730 views
  8. Daily Leetcode #862 part 2: Aug 10, 2022 - 393. UTF-8 ValidationProgramming Live with Larry · 15:00 · 648 views
  9. leetcode 393 UTF-8 Validation: understanding the questionIke Feng · 3:22 · 266 views
  10. 393. UTF-8 Validation || DSA || Leetcode || Daily ChallengeLeetcode Daily Challenge · 7:41 · 245 views

The problem

Given an integer array data representing the data, return whether it is a valid UTF-8 encoding (i.e. it translates to a sequence of valid UTF-8 encoded characters).

A character in UTF8 can be from 1 to 4 bytes long, subjected to the following rules:

  1. For a 1-byte character, the first bit is a 0, followed by its Unicode code.
  2. For an n-bytes character, the first n bits are all one's, the n + 1 bit is 0, followed by n - 1 bytes with the most significant 2 bits being 10.

This is how the UTF-8 encoding would work:

     Number of Bytes   |        UTF-8 Octet Sequence
                       |              (binary)
   --------------------+-----------------------------------------
            1          |   0xxxxxxx
            2          |   110xxxxx 10xxxxxx
            3          |   1110xxxx 10xxxxxx 10xxxxxx
            4          |   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

x denotes a bit in the binary form of a byte that may be either 0 or 1.

Note: The input is an array of integers. Only the least significant 8 bits of each integer is used to store the data. This means each integer represents only 1 byte of data.

Example 1:

Input: data = [197,130,1]
Output: true
Explanation: data represents the octet sequence: 11000101 10000010 00000001.
It is a valid utf-8 encoding for a 2-bytes character followed by a 1-byte character.

Example 2:

Input: data = [235,140,4]
Output: false
Explanation: data represented the octet sequence: 11101011 10001100 00000100.
The first 3 bits are all one's and the 4th bit is 0 means it is a 3-bytes character.
The next byte is a continuation byte which starts with 10 and that's correct.
But the second continuation byte does not start with 10, so it is invalid.

Constraints:

  • 1 <= data.length <= 2 * 104
  • 0 <= data[i] <= 255

Approach overview

Problem Overview: You receive an array of integers where each value represents a byte (only the lowest 8 bits are used). Determine whether the sequence forms a valid UTF-8 encoded character stream. UTF-8 characters may use 1 to 4 bytes, and continuation bytes must follow strict bit patterns.

Approach 1: Iterative Bit Pattern Validation (O(n) time, O(1) space)

This approach scans the array once while tracking how many continuation bytes are expected for the current UTF‑8 character. UTF‑8 encodings follow specific leading bit patterns: 0xxxxxxx for 1 byte, 110xxxxx for 2 bytes, 1110xxxx for 3 bytes, and 11110xxx for 4 bytes. Continuation bytes must always match 10xxxxxx. Iterate through the array and inspect the highest bits using bit masking and shifting. If a leading byte is encountered, determine how many continuation bytes should follow. For each expected continuation byte, verify the 10 prefix. If a mismatch occurs or the sequence ends early, the encoding is invalid. This method uses constant extra memory and relies purely on bit checks, making it the most practical solution. Problems like this commonly appear in bit manipulation practice where understanding binary prefixes is critical.

Approach 2: Recursive Sequence Validation (O(n) time, O(n) recursion depth worst case)

The recursive approach models the validation as processing one UTF‑8 character at a time. Start from index i, inspect the leading byte, and determine the total number of bytes in the character using the prefix bits. Then recursively validate the required continuation bytes and move to the next character start index. Each continuation byte must match the 10xxxxxx pattern before recursion proceeds. This method makes the structure of UTF‑8 characters explicit, but recursion adds overhead and stack usage. While still linear time, it consumes additional call stack space and is less practical than the iterative scan. However, it can help when reasoning about parsing sequences in array traversal problems or recursive parsers.

Recommended for interviews: The iterative bit‑masking approach is what interviewers typically expect. It demonstrates that you understand UTF‑8 byte prefixes and can efficiently validate them with bit operations. The recursive approach proves conceptual understanding of character grouping, but the iterative version shows stronger control over bit manipulation and constant‑space streaming validation.

Approach 1: Approach 1: Iterative Solution

This approach involves solving the problem using an iterative method, where we use loops to perform the necessary calculations. This can be more efficient in terms of space complexity, especially if recursion would lead to excessive function call overhead.

def solve_problem(n):
    for i in range(n):
        # Solution logic here
        print(i, end=' ')

n = 10
solve_problem(n)
public class Solution {
    public static void solveProblem(int n) {
        for (int i = 0; i < n; i++) {
            // Solution logic here
            System.out.print(i + " ");
        }
    }

    public static void main(String[] args) {
        int n = 10;
        solveProblem(n);
    }
}
#include <iostream>
using namespace std;

void solveProblem(int n) {
    for (int i = 0; i < n; ++i) {
        // Implementation of the solution logic here
        cout << i << " ";
    }
}

int main() {
    int n = 10;
    solveProblem(n);
    return 0;
}
function solveProblem(n) {
    for (let i = 0; i < n; i++) {
        // Solution logic here
        process.stdout.write(i + ' ');
    }
}

const n = 10;
solveProblem(n);
#include <stdio.h>

void solveProblem(int n) {
    for (int i = 0; i < n; ++i) {
        // Implementation of the solution logic here
        printf("%d ", i);
    }
}

int main() {
    int n = 10;
    solveProblem(n);
    return 0;
}
using System;

class Solution {
    public static void SolveProblem(int n) {
        for (int i = 0; i < n; i++) {
            // Solution logic here
            Console.Write(i + " ");
        }
    }

    static void Main(string[] args) {
        int n = 10;
        SolveProblem(n);
    }
}

Python's range function is used to iterate through numbers, printing each with a simple print statement, achieving a straightforward iterative solution.

Complexity

Time Complexity: O(n)
Space Complexity: O(1)

Try this approach in the editor

Approach 2: Approach 2: Recursive Solution

This approach explores solving the problem through recursion, which can offer simplicity and expressiveness. However, care must be taken with recursion depth to avoid stack overflow.

def solve_problem(n, i=0):
    if i >= n:
        return
    # Solution logic here
    print(i, end=' ')
    solve_problem(n, i + 1)

n = 10
solve_problem(n)
public class Solution {
    public static void solveProblem(int n, int i) {
        if (i >= n) return;
        // Solution logic here
        System.out.print(i + " ");
        solveProblem(n, i + 1);
    }

    public static void main(String[] args) {
        int n = 10;
        solveProblem(n, 0);
    }
}
#include <iostream>
using namespace std;

void solveProblem(int n, int i) {
    if (i >= n) return;
    // Solution logic here
    cout << i << " ";
    solveProblem(n, i + 1);
}

int main() {
    int n = 10;
    solveProblem(n, 0);
    return 0;
}
function solveProblem(n, i=0) {
    if (i >= n) return;
    // Solution logic here
    process.stdout.write(i + ' ');
    solveProblem(n, i + 1);
}

const n = 10;
solveProblem(n);
#include <stdio.h>

void solveProblem(int n, int i) {
    if (i >= n) return;
    // Solution logic here
    printf("%d ", i);
    solveProblem(n, i + 1);
}

int main() {
    int n = 10;
    solveProblem(n, 0);
    return 0;
}
using System;

class Solution {
    public static void SolveProblem(int n, int i) {
        if (i >= n) return;
        // Solution logic here
        Console.Write(i + " ");
        SolveProblem(n, i + 1);
    }

    static void Main(string[] args) {
        int n = 10;
        SolveProblem(n, 0);
    }
}

Python's recursive implementation uses a default parameter `i` to control recursive depth, making calls until `i` equals `n` and printing during each call.

Complexity

Time Complexity: O(n)
Space Complexity: O(n)

Try this approach in the editor

Approach 3: Single Pass

We use a variable cnt to record the current number of bytes that need to be filled starting with 10, initially cnt = 0.

For each integer v in the array:

  • If cnt > 0, then check if v starts with 10. If not, return false, otherwise decrement cnt.
  • If the highest bit of v is 0, then cnt = 0.
  • If the highest two bits of v are 110, then cnt = 1.
  • If the highest three bits of v are 1110, then cnt = 2.
  • If the highest four bits of v are 11110, then cnt = 3.
  • Otherwise, return false.

Finally, if cnt = 0, return true, otherwise return false.

The time complexity is O(n), where n is the length of the array data. The space complexity is O(1).

function validUtf8(data: number[]): boolean {
    let cnt = 0;
    for (const v of data) {
        if (cnt > 0) {
            if (v >> 6 !== 0b10) {
                return false;
            }
            --cnt;
        } else if (v >> 7 === 0) {
            cnt = 0;
        } else if (v >> 5 === 0b110) {
            cnt = 1;
        } else if (v >> 4 === 0b1110) {
            cnt = 2;
        } else if (v >> 3 === 0b11110) {
            cnt = 3;
        } else {
            return false;
        }
    }
    return cnt === 0;
}
func validUtf8(data []int) bool {
	cnt := 0
	for _, v := range data {
		if cnt > 0 {
			if v>>6 != 0b10 {
				return false
			}
			cnt--
		} else if v>>7 == 0 {
			cnt = 0
		} else if v>>5 == 0b110 {
			cnt = 1
		} else if v>>4 == 0b1110 {
			cnt = 2
		} else if v>>3 == 0b11110 {
			cnt = 3
		} else {
			return false
		}
	}
	return cnt == 0
}
Try this approach in the editor

Complexity comparison

ApproachComplexity
Approach 1: Iterative Solution

Time Complexity: O(n)
Space Complexity: O(1)

Approach 2: Recursive Solution

Time Complexity: O(n)
Space Complexity: O(n)

Single Pass–

Complexity analysis

ApproachTimeSpaceWhen to Use
Iterative Bit Pattern ValidationO(n)O(1)Best general solution. Single pass validation using bit masks and counters.
Recursive Sequence ValidationO(n)O(n) worst case recursionUseful for conceptual parsing of UTF‑8 characters or recursive stream validation.

Frequently asked questions

Is UTF-8 Validation easy or hard?

UTF-8 Validation is generally classified as a medium difficulty problem. The challenge comes from understanding UTF‑8 byte patterns and implementing precise bit checks. Once the encoding rules are clear, the implementation becomes a straightforward linear scan.

UTF-8 Validation Python/Java solution

Python and Java solutions typically implement the iterative bit-mask approach. In Python, bit shifting and masking are used to detect prefix bits, while Java uses integer bit operations for the same checks. Both versions achieve O(n) time and O(1) space.

How to solve UTF-8 Validation in O(n)?

Traverse the byte array while tracking the number of continuation bytes required. For each leading byte, determine its type using bit masks such as 110, 1110, or 11110. Then ensure the following bytes match the continuation pattern 10xxxxxx. Each element is processed once, giving O(n) time complexity.

What is the best approach for UTF-8 Validation?

The iterative bit‑manipulation approach is the most efficient and widely used solution. It scans the array once, determines how many continuation bytes are required for each leading byte, and verifies their bit pattern. The algorithm runs in O(n) time with O(1) extra space, making it ideal for interviews and production code.

Is UTF-8 Validation asked at Google/Amazon/Meta?

UTF-8 parsing and bit manipulation questions appear in interviews at companies like Google, Amazon, and Meta, especially for backend and systems roles. The problem tests understanding of binary representation, encoding rules, and careful iteration through arrays.

What data structure is used in UTF-8 Validation?

The problem mainly uses arrays and bit manipulation operations. The algorithm iterates through the array and uses bit masks or shifts to inspect leading bits in each byte. No complex data structures are required beyond simple counters.

What is the time complexity of UTF-8 Validation?

UTF-8 Validation can be solved in O(n) time because each byte in the array is examined once. The algorithm only performs constant-time bit checks and counter updates per element. Space complexity is O(1) when implemented iteratively.

Comments

Now write it yourselfClose this page and solve UTF-8 Validation in the editor, without looking.
Practise UTF-8 Validation