diff --git a/src/index.spec.ts b/src/index.spec.ts
index eba64684..b78fc802 100644
--- a/src/index.spec.ts
+++ b/src/index.spec.ts
@@ -24,6 +24,7 @@ import { TraceSource } from "./trace/trace-context-service";
 import { inflateSync } from "zlib";
 import { MetricsListener } from "./metrics/listener";
 import { SpanOptions, TracerWrapper } from "./trace/tracer-wrapper";
+import fs from "fs";
 
 jest.mock("./metrics/enhanced-metrics");
 
@@ -623,3 +624,78 @@ describe("emitTelemetryOnErrorOutsideHandler", () => {
     expect(mockedStartSpan).toBeCalledTimes(0);
   });
 });
+
+describe("detectDuplicateInstallations", () => {
+  jest.mock("fs/promises");
+
+  let fsAccessMock: jest.SpyInstance;
+  let logWarningMock: jest.SpyInstance;
+
+  beforeEach(() => {
+    jest.resetAllMocks();
+    fsAccessMock = jest.spyOn(fs.promises, "access");
+    logWarningMock = jest.spyOn(require("./utils"), "logWarning").mockImplementation(() => {});
+  });
+
+  it("should log warning when duplicate installations are detected", async () => {
+    // Mock fs.promises.access to simulate both paths exist
+    fsAccessMock.mockResolvedValue(undefined); // undefined (no error) = path exists
+
+    await datadog(
+      async () => {
+        /* empty */
+      },
+      { forceWrap: true },
+    )();
+    expect(logWarningMock).toHaveBeenCalledWith(expect.stringContaining("Detected duplicate installations"));
+  });
+
+  it("should not log warning when only layer installation exists", async () => {
+    // Simulate layerPath exists, localPath does not exist
+    fsAccessMock.mockImplementation((path: string) => {
+      if (path.includes("/opt/nodejs")) {
+        return Promise.resolve(); // Exists
+      }
+      return Promise.reject(new Error("ENOENT")); // Does not exist
+    });
+
+    await datadog(
+      async () => {
+        /* empty */
+      },
+      { forceWrap: true },
+    )();
+    expect(logWarningMock).not.toHaveBeenCalled();
+  });
+
+  it("should not log warning when only local installation exists", async () => {
+    // Simulate localPath exists, layerPath does not exist
+    fsAccessMock.mockImplementation((path: string) => {
+      if (path.includes("/opt/nodejs")) {
+        return Promise.reject(new Error("ENOENT")); // Does not exist
+      }
+      return Promise.resolve(); // Exists
+    });
+
+    await datadog(
+      async () => {
+        /* empty */
+      },
+      { forceWrap: true },
+    )();
+    expect(logWarningMock).not.toHaveBeenCalled();
+  });
+
+  it("should not log warning when neither installation exists", async () => {
+    // Simulate neither path exists
+    fsAccessMock.mockRejectedValue(new Error("ENOENT")); // Does not exist
+
+    await datadog(
+      async () => {
+        /* empty */
+      },
+      { forceWrap: true },
+    )();
+    expect(logWarningMock).not.toHaveBeenCalled();
+  });
+});
diff --git a/src/index.ts b/src/index.ts
index 1d401e4f..266612fd 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -21,11 +21,14 @@ import {
   setSandboxInit,
   setLogger,
   setLogLevel,
+  logWarning,
 } from "./utils";
 import { getEnhancedMetricTags } from "./metrics/enhanced-metrics";
 import { DatadogTraceHeaders } from "./trace/context/extractor";
 import { SpanWrapper } from "./trace/span-wrapper";
 import { SpanOptions, TracerWrapper } from "./trace/tracer-wrapper";
+import path from "path";
+import fs from "fs";
 
 // Backwards-compatible export, TODO deprecate in next major
 export { DatadogTraceHeaders as TraceHeaders } from "./trace/context/extractor";
@@ -104,6 +107,9 @@ export const _metricsQueue: MetricsQueue = new MetricsQueue();
 let currentMetricsListener: MetricsListener | undefined;
 let currentTraceListener: TraceListener | undefined;
 
+const LAMBDA_LAYER_PATH = "/opt/nodejs/node_modules/datadog-lambda-js";
+const LAMBDA_LIBRARY_PATH = path.join(process.cwd(), "node_modules/datadog-lambda-js");
+
 if (getEnvValue(coldStartTracingEnvVar, "true").toLowerCase() === "true") {
   subscribeToDC();
 }
@@ -131,6 +137,19 @@ export function datadog<TEvent, TResult>(
 
   const traceListener = new TraceListener(finalConfig);
 
+  // Check for duplicate installations of the Lambda library
+  detectDuplicateInstallations()
+    .then((duplicateFound) => {
+      if (duplicateFound) {
+        logWarning(
+          `Detected duplicate installations of datadog-lambda-js. This can cause: (1) increased cold start times, (2) broken metrics, and (3) other unexpected behavior. Please use either the Lambda layer version or the package in node_modules, but not both. See: https://docs.datadoghq.com/serverless/aws_lambda/installation/nodejs/?tab=custom`,
+        );
+      }
+    })
+    .catch(() => {
+      logDebug("Failed to check for duplicate installations.");
+    });
+
   // Only wrap the handler once unless forced
   const _ddWrappedKey = "_ddWrapped";
   if ((handler as any)[_ddWrappedKey] !== undefined && !finalConfig.forceWrap) {
@@ -478,3 +497,26 @@ export async function emitTelemetryOnErrorOutsideHandler(
     await metricsListener.onCompleteInvocation();
   }
 }
+
+async function detectDuplicateInstallations() {
+  try {
+    const checkPathExistsAsync = async (libraryPath: string): Promise<boolean> => {
+      try {
+        await fs.promises.access(libraryPath);
+        return true;
+      } catch {
+        return false;
+      }
+    };
+
+    const [layerExists, localExists] = await Promise.all([
+      checkPathExistsAsync(LAMBDA_LAYER_PATH),
+      checkPathExistsAsync(LAMBDA_LIBRARY_PATH),
+    ]);
+
+    return layerExists && localExists;
+  } catch (err) {
+    logDebug("Failed to check for duplicate installations.");
+    return false;
+  }
+}