7 examples

Off-by-one error

Calculation errors from loop or array indexes off by one.

[ FAQ1 ]

What is an off-by-one error?

An off-by-one error occurs when calculations, indexing, or loop bounds are off by a single value, typically due to incorrect logic involving array indexes or iteration conditions. These mistakes are often called fencepost errors, referring to the subtle boundary miscalculations common in loops or ranges. Off-by-one errors produce subtle bugs, such as accessing the wrong array elements, missing the last item in a loop, or iterating one step too far, often causing application logic issues, data inaccuracies, or runtime exceptions.
[ FAQ2 ]

How to fix off-by-one errors

To fix off-by-one errors, carefully verify loop conditions, array indexes, and boundary conditions, explicitly checking whether loop ranges start and end exactly as intended. Clearly document and review indexing logic, emphasizing zero-based indexing conventions typically used in programming languages. Use debugging tools and tests to systematically confirm correctness at loop boundaries or index operations, focusing explicitly on edge cases. Regular code reviews, automated testing, and clear logic structuring significantly reduce the occurrence of off-by-one mistakes.
diff block
+import { format, differenceInDays } from "date-fns";
+
+export function formatRelativeDate(dateString: string) {
+ const date = new Date(dateString);
+ const now = new Date();
+ const days = differenceInDays(now, date);
+ if (days < 1) return "1d";
+ if (days < 7) return `${days + 1}d`;
Greptile
greptile
logic: Adding 1 to days when days &lt; 7 but not when days &lt; 1 creates inconsistent behavior. This could cause off-by-one errors in date displays.
diff block
-import { getPosts } from '@/app/utils/utils';
-import { Flex } from '@/once-ui/components';
+import { getPosts } from "@/app/utils/utils";
+import { Flex } from "@/once-ui/components";
-import { ProjectCard } from '@/components';
+import { ProjectCard } from "@/components";
interface ProjectsProps {
- range?: [number, number?];
- locale: string;
+ range?: [number, number?];
+ locale: string;
}
export function Projects({ range, locale }: ProjectsProps) {
- let allProjects = getPosts(['src', 'app', '[locale]', 'work', 'projects', locale]);
-
- const sortedProjects = allProjects.sort((a, b) => {
- return new Date(b.metadata.publishedAt).getTime() - new Date(a.metadata.publishedAt).getTime();
- });
-
- const displayedProjects = range
- ? sortedProjects.slice(range[0] - 1, range[1] ?? sortedProjects.length)
- : sortedProjects;
+ let allProjects = getPosts([
+ "src",
+ "app",
+ "[locale]",
+ "work",
+ "projects",
+ locale,
+ ]);
+ const sortedProjects = allProjects.sort((a, b) => {
return (
- <Flex
- fillWidth gap="xl" marginBottom="40" paddingX="l"
- direction="column">
- {displayedProjects.map((post) => (
- <ProjectCard
- key={post.slug}
- href={`work/${post.slug}`}
- images={post.metadata.images}
- title={post.metadata.title}
- description={post.metadata.summary}
- content={post.content}
- avatars={post.metadata.team?.map((member) => ({ src: member.avatar })) || []}/>
- ))}
- </Flex>
+ new Date(b.metadata.publishedAt).getTime() -
+ new Date(a.metadata.publishedAt).getTime()
);
-}
\ No newline at end of file
+ });
+
+ const displayedProjects = range
+ ? sortedProjects.slice(range[0] - 1, range[1] ?? sortedProjects.length)
+ : sortedProjects;
Greptile
greptile
logic: potential off-by-one error in slice operation when range[0] is 0
suggested fix
const displayedProjects = range
+ ? sortedProjects.slice(Math.max(0, range[0] - 1), range[1] ?? sortedProjects.length)
: sortedProjects;
diff block
data = val[0]
+ # Calculate cumulative values if requested
+ if (
+ self.query.stickinessFilter
+ and self.query.stickinessFilter.computedAs == StickinessComputationMode.CUMULATIVE
+ ):
+ cumulative_data = []
+ for i in range(len(data)):
+ total_for_days = sum(data[i:])
+ cumulative_data.append(total_for_days)
+ data = cumulative_data
Greptile
greptile
logic: potential off-by-one error in cumulative calculation - verify that i:end slicing gives correct totals for edge cases
diff block
+import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common';
+
+import { DatabaseConfigDriverInterface } from 'src/engine/core-modules/twenty-config/interfaces/database-config-driver.interface';
+
+import { ConfigCacheService } from 'src/engine/core-modules/twenty-config/cache/config-cache.service';
+import { ConfigVariables } from 'src/engine/core-modules/twenty-config/config-variables';
+import { DATABASE_CONFIG_DRIVER_INITIAL_RETRY_DELAY } from 'src/engine/core-modules/twenty-config/constants/database-config-driver-initial-retry-delay';
+import { DATABASE_CONFIG_DRIVER_INITIALIZATION_MAX_RETRIES } from 'src/engine/core-modules/twenty-config/constants/database-config-driver-initialization-max-retries';
+import { InitializationState } from 'src/engine/core-modules/twenty-config/enums/initialization-state.enum';
+import { ConfigStorageService } from 'src/engine/core-modules/twenty-config/storage/config-storage.service';
+import { isEnvOnlyConfigVar } from 'src/engine/core-modules/twenty-config/utils/is-env-only-config-var.util';
+
+import { EnvironmentConfigDriver } from './environment-config.driver';
+
+@Injectable()
+export class DatabaseConfigDriver
+ implements DatabaseConfigDriverInterface, OnModuleDestroy
+{
+ private initializationState = InitializationState.NOT_INITIALIZED;
+ private initializationPromise: Promise<void> | null = null;
+ private retryAttempts = 0;
+ private retryTimer?: NodeJS.Timeout;
+ private readonly logger = new Logger(DatabaseConfigDriver.name);
+
+ constructor(
+ private readonly configCache: ConfigCacheService,
+ private readonly configStorage: ConfigStorageService,
+ private readonly environmentDriver: EnvironmentConfigDriver,
+ ) {}
+
+ async initialize(): Promise<void> {
+ if (this.initializationPromise) {
+ return this.initializationPromise;
+ }
+
+ this.initializationPromise = this.doInitialize();
+
+ return this.initializationPromise;
+ }
+
+ private async doInitialize(): Promise<void> {
+ try {
+ await this.loadAllConfigVarsFromDb();
+ this.initializationState = InitializationState.INITIALIZED;
+ } catch (error) {
+ this.logger.error('Failed to initialize database driver', error);
+ this.initializationState = InitializationState.FAILED;
+ this.scheduleRetry();
+ }
+ }
+
+ get<T extends keyof ConfigVariables>(key: T): ConfigVariables[T] {
+ if (this.shouldUseEnvironment(key)) {
+ this.logger.debug(
+ `[Config:${key}] Using env due to ${
+ this.initializationState !== InitializationState.INITIALIZED
+ ? 'initialization state'
+ : 'isEnvOnly flag'
+ }`,
+ );
+
+ return this.environmentDriver.get(key);
+ }
+
+ const cachedValue = this.configCache.get(key);
+
+ if (cachedValue !== undefined) {
+ return cachedValue;
+ }
+
+ if (this.configCache.getNegativeLookup(key)) {
+ return this.environmentDriver.get(key);
+ }
+
+ this.scheduleRefresh(key);
+
+ return this.environmentDriver.get(key);
+ }
+
+ async update<T extends keyof ConfigVariables>(
+ key: T,
+ value: ConfigVariables[T],
+ ): Promise<void> {
+ if (this.shouldUseEnvironment(key)) {
+ throw new Error(
+ `Cannot update environment-only variable: ${key as string}`,
+ );
+ }
+
+ await this.configStorage.set(key, value);
+ this.configCache.set(key, value);
+ }
+
+ clearCache(key: keyof ConfigVariables): void {
+ this.configCache.clear(key);
+ }
+
+ clearAllCache(): void {
+ this.configCache.clearAll();
+ }
+
+ async refreshConfig(key: keyof ConfigVariables): Promise<void> {
+ try {
+ const value = await this.configStorage.get(key);
+
+ if (value !== undefined) {
+ this.configCache.set(key, value);
+ } else {
+ this.configCache.setNegativeLookup(key);
+ }
+ } catch (error) {
+ this.logger.error(`Failed to refresh config for ${key as string}`, error);
+ this.configCache.setNegativeLookup(key);
+ }
+ }
+
+ getCacheInfo(): {
+ positiveEntries: number;
+ negativeEntries: number;
+ cacheKeys: string[];
+ } {
+ return this.configCache.getCacheInfo();
+ }
+
+ private shouldUseEnvironment(key: keyof ConfigVariables): boolean {
+ return (
+ this.initializationState !== InitializationState.INITIALIZED ||
+ isEnvOnlyConfigVar(key)
+ );
+ }
+
+ private async loadAllConfigVarsFromDb(): Promise<void> {
+ try {
+ const configVars = await this.configStorage.loadAll();
+
+ for (const [key, value] of configVars.entries()) {
+ this.configCache.set(key, value);
+ }
+
+ this.logger.log(
+ `Loaded ${configVars.size} config variables from database`,
+ );
+ } catch (error) {
+ this.logger.error('Failed to load config variables from database', error);
+ throw error;
+ }
+ }
+
+ private async scheduleRefresh(key: keyof ConfigVariables): Promise<void> {
+ if (this.initializationState !== InitializationState.INITIALIZED) {
+ this.logger.debug(
+ `[Config:${key}] Skipping refresh due to initialization state`,
+ );
+
+ return;
+ }
+
+ this.logger.debug(`🕒 [Config:${key}] Scheduling background refresh`);
+
+ try {
+ await Promise.resolve().then(async () => {
+ this.logger.debug(`⏳ [Config:${key}] Executing background refresh`);
+ await this.refreshConfig(key);
+ });
+ } catch (error) {
+ this.logger.error(`Failed to refresh config for ${key as string}`, error);
+ }
+ }
+
+ private scheduleRetry(): void {
+ if (
+ this.retryAttempts >= DATABASE_CONFIG_DRIVER_INITIALIZATION_MAX_RETRIES
+ ) {
+ this.logger.error('Max retry attempts reached, giving up initialization');
+
+ return;
+ }
+
+ const delay =
+ DATABASE_CONFIG_DRIVER_INITIAL_RETRY_DELAY *
+ Math.pow(2, this.retryAttempts);
+
+ this.retryAttempts++;
Greptile
greptile
style: Incrementing retryAttempts before checking against max retries could lead to an off-by-one error. Consider moving this line before the if-check at line 171.
diff block
+package main
+
+import "fmt"
+
+func sequentialSearch(arrPtr *[]int, target int) bool {
+ arrLength := len(*arrPtr)
+ for i := 0; i < arrLength; i++ {
+ if (*arrPtr)[i] == target {
+ return true
+ }
+ }
+ return false
Greptile
greptile
style: Consider using range-based iteration instead of index-based for cleaner code and to avoid potential off-by-one errors.
diff block
+package main
+
+import "fmt"
+
+func sequentialSearch(arrPtr *[]int, target int) bool {
+ arrLength := len(*arrPtr)
+ for i := 0; i < arrLength; i++ {
+ if (*arrPtr)[i] == target {
+ return true
+ }
+ }
+ return false
Greptile
greptile
Off-by-one errors are a common programming mistake where a loop or array access is performed one time too many or one time too few. They typically happen when: 1. Using incorrect comparison operators (< vs <=) 2. Starting loops at the wrong index (0 vs 1) 3. Miscalculating array boundaries In your code, you're correctly using `i < arrLength` which avoids an off-by-one error (if you had used `i <= arrLength`, you would access an element outside the array bounds). For cleaner code, you could use a range-based loop instead of an index-based one. This eliminates the need to manually track indices and reduces the chance of off-by-one errors:
suggested fix
func sequentialSearch(arrPtr *[]int, target int) bool {
+ for _, value := range *arrPtr {
+ if value == target {
return true
}
}
return false
}
This approach is more idiomatic in Go and less error-prone since the range loop automatically handles the iteration boundaries for you.
diff block
pub fn get_hostname() -> String {
sysinfo::System::host_name().unwrap_or("unknown".to_string())
}
+
+#[cfg(target_os = "linux")]
+pub fn get_open_fds() -> usize {
+ match std::fs::read_dir("/proc/self/fd") {
+ Ok(entries) => entries.count(),
+ Err(e) => {
Greptile
greptile
logic: entries.count() includes the file descriptor used to read the directory itself, leading to an off-by-one error. Consider filtering out the current fd or subtracting 1 from the final count.